mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-03 08:53:36 +00:00
feat: Integrate Cursor provider support in AI profiles
- Updated AIProfile type to include support for Cursor provider, adding cursorModel and validation logic. - Enhanced ProfileForm component to handle provider selection and corresponding model configurations for both Claude and Cursor. - Implemented display functions for model and thinking configurations in ProfileQuickSelect. - Added default Cursor profiles to the application state. - Updated UI components to reflect provider-specific settings and validations. - Marked completion of the AI Profiles Integration phase in the project plan.
This commit is contained in:
@@ -1,9 +1,35 @@
|
|||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Brain, UserCircle } from 'lucide-react';
|
import { Brain, UserCircle } from 'lucide-react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { AgentModel, ThinkingLevel, AIProfile } from '@/store/app-store';
|
import type { AgentModel, ThinkingLevel, AIProfile } from '@automaker/types';
|
||||||
|
import { CURSOR_MODEL_MAP, profileHasThinking } from '@automaker/types';
|
||||||
import { PROFILE_ICONS } from './model-constants';
|
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;
|
||||||
|
}
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
// Claude
|
||||||
|
return profile.thinkingLevel && profile.thinkingLevel !== 'none' ? profile.thinkingLevel : null;
|
||||||
|
}
|
||||||
|
|
||||||
interface ProfileQuickSelectProps {
|
interface ProfileQuickSelectProps {
|
||||||
profiles: AIProfile[];
|
profiles: AIProfile[];
|
||||||
selectedModel: AgentModel;
|
selectedModel: AgentModel;
|
||||||
@@ -23,7 +49,11 @@ export function ProfileQuickSelect({
|
|||||||
showManageLink = false,
|
showManageLink = false,
|
||||||
onManageLinkClick,
|
onManageLinkClick,
|
||||||
}: ProfileQuickSelectProps) {
|
}: ProfileQuickSelectProps) {
|
||||||
if (profiles.length === 0) {
|
// 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');
|
||||||
|
|
||||||
|
if (claudeProfiles.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +69,7 @@ export function ProfileQuickSelect({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
{profiles.slice(0, 6).map((profile) => {
|
{claudeProfiles.slice(0, 6).map((profile) => {
|
||||||
const IconComponent = profile.icon ? PROFILE_ICONS[profile.icon] : Brain;
|
const IconComponent = profile.icon ? PROFILE_ICONS[profile.icon] : Brain;
|
||||||
const isSelected =
|
const isSelected =
|
||||||
selectedModel === profile.model && selectedThinkingLevel === profile.thinkingLevel;
|
selectedModel === profile.model && selectedThinkingLevel === profile.thinkingLevel;
|
||||||
@@ -47,7 +77,7 @@ export function ProfileQuickSelect({
|
|||||||
<button
|
<button
|
||||||
key={profile.id}
|
key={profile.id}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onSelect(profile.model, profile.thinkingLevel)}
|
onClick={() => onSelect(profile.model!, profile.thinkingLevel!)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-2 p-2 rounded-lg border text-left transition-all',
|
'flex items-center gap-2 p-2 rounded-lg border text-left transition-all',
|
||||||
isSelected
|
isSelected
|
||||||
@@ -62,8 +92,8 @@ export function ProfileQuickSelect({
|
|||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<p className="text-sm font-medium truncate">{profile.name}</p>
|
<p className="text-sm font-medium truncate">{profile.name}</p>
|
||||||
<p className="text-[10px] text-muted-foreground truncate">
|
<p className="text-[10px] text-muted-foreground truncate">
|
||||||
{profile.model}
|
{getProfileModelDisplay(profile)}
|
||||||
{profile.thinkingLevel !== 'none' && ` + ${profile.thinkingLevel}`}
|
{getProfileThinkingDisplay(profile) && ` + ${getProfileThinkingDisplay(profile)}`}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -4,13 +4,20 @@ import { HotkeyButton } from '@/components/ui/hotkey-button';
|
|||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { cn, modelSupportsThinking } from '@/lib/utils';
|
import { cn, modelSupportsThinking } from '@/lib/utils';
|
||||||
import { DialogFooter } from '@/components/ui/dialog';
|
import { DialogFooter } from '@/components/ui/dialog';
|
||||||
import { Brain } from 'lucide-react';
|
import { Brain, Bot, Terminal } from 'lucide-react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import type { AIProfile, AgentModel, ThinkingLevel } from '@/store/app-store';
|
import type {
|
||||||
|
AIProfile,
|
||||||
|
AgentModel,
|
||||||
|
ThinkingLevel,
|
||||||
|
ModelProvider,
|
||||||
|
CursorModelId,
|
||||||
|
} from '@automaker/types';
|
||||||
|
import { CURSOR_MODEL_MAP, cursorModelHasThinking } from '@automaker/types';
|
||||||
import { CLAUDE_MODELS, THINKING_LEVELS, ICON_OPTIONS } from '../constants';
|
import { CLAUDE_MODELS, THINKING_LEVELS, ICON_OPTIONS } from '../constants';
|
||||||
import { getProviderFromModel } from '../utils';
|
|
||||||
|
|
||||||
interface ProfileFormProps {
|
interface ProfileFormProps {
|
||||||
profile: Partial<AIProfile>;
|
profile: Partial<AIProfile>;
|
||||||
@@ -30,13 +37,27 @@ export function ProfileForm({
|
|||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
name: profile.name || '',
|
name: profile.name || '',
|
||||||
description: profile.description || '',
|
description: profile.description || '',
|
||||||
model: profile.model || ('opus' as AgentModel),
|
provider: (profile.provider || 'claude') as ModelProvider,
|
||||||
|
// Claude-specific
|
||||||
|
model: profile.model || ('sonnet' as AgentModel),
|
||||||
thinkingLevel: profile.thinkingLevel || ('none' as ThinkingLevel),
|
thinkingLevel: profile.thinkingLevel || ('none' as ThinkingLevel),
|
||||||
|
// Cursor-specific
|
||||||
|
cursorModel: profile.cursorModel || ('auto' as CursorModelId),
|
||||||
icon: profile.icon || 'Brain',
|
icon: profile.icon || 'Brain',
|
||||||
});
|
});
|
||||||
|
|
||||||
const provider = getProviderFromModel(formData.model);
|
const supportsThinking = formData.provider === 'claude' && modelSupportsThinking(formData.model);
|
||||||
const supportsThinking = modelSupportsThinking(formData.model);
|
|
||||||
|
const handleProviderChange = (provider: ModelProvider) => {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
provider,
|
||||||
|
// Reset to defaults when switching providers
|
||||||
|
model: provider === 'claude' ? 'sonnet' : formData.model,
|
||||||
|
thinkingLevel: provider === 'claude' ? 'none' : formData.thinkingLevel,
|
||||||
|
cursorModel: provider === 'cursor' ? 'auto' : formData.cursorModel,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleModelChange = (model: AgentModel) => {
|
const handleModelChange = (model: AgentModel) => {
|
||||||
setFormData({
|
setFormData({
|
||||||
@@ -45,21 +66,39 @@ export function ProfileForm({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCursorModelChange = (cursorModel: CursorModelId) => {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
cursorModel,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
if (!formData.name.trim()) {
|
if (!formData.name.trim()) {
|
||||||
toast.error('Please enter a profile name');
|
toast.error('Please enter a profile name');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
onSave({
|
const baseProfile = {
|
||||||
name: formData.name.trim(),
|
name: formData.name.trim(),
|
||||||
description: formData.description.trim(),
|
description: formData.description.trim(),
|
||||||
model: formData.model,
|
provider: formData.provider,
|
||||||
thinkingLevel: supportsThinking ? formData.thinkingLevel : 'none',
|
|
||||||
provider,
|
|
||||||
isBuiltIn: false,
|
isBuiltIn: false,
|
||||||
icon: formData.icon,
|
icon: formData.icon,
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (formData.provider === 'cursor') {
|
||||||
|
onSave({
|
||||||
|
...baseProfile,
|
||||||
|
cursorModel: formData.cursorModel,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
onSave({
|
||||||
|
...baseProfile,
|
||||||
|
model: formData.model,
|
||||||
|
thinkingLevel: supportsThinking ? formData.thinkingLevel : 'none',
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -113,34 +152,128 @@ export function ProfileForm({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Model Selection */}
|
{/* Provider Selection */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="flex items-center gap-2">
|
<Label>AI Provider</Label>
|
||||||
<Brain className="w-4 h-4 text-primary" />
|
<div className="flex gap-2">
|
||||||
Model
|
<button
|
||||||
</Label>
|
type="button"
|
||||||
<div className="flex gap-2 flex-wrap">
|
onClick={() => handleProviderChange('claude')}
|
||||||
{CLAUDE_MODELS.map(({ id, label }) => (
|
className={cn(
|
||||||
<button
|
'flex-1 px-3 py-2 rounded-md border text-sm font-medium transition-colors flex items-center justify-center gap-2',
|
||||||
key={id}
|
formData.provider === 'claude'
|
||||||
type="button"
|
? 'bg-primary text-primary-foreground border-primary'
|
||||||
onClick={() => handleModelChange(id)}
|
: 'bg-background hover:bg-accent border-border'
|
||||||
className={cn(
|
)}
|
||||||
'flex-1 min-w-[100px] px-3 py-2 rounded-md border text-sm font-medium transition-colors',
|
data-testid="provider-select-claude"
|
||||||
formData.model === id
|
>
|
||||||
? 'bg-primary text-primary-foreground border-primary'
|
<Bot className="w-4 h-4" />
|
||||||
: 'bg-background hover:bg-accent border-border'
|
Claude
|
||||||
)}
|
</button>
|
||||||
data-testid={`model-select-${id}`}
|
<button
|
||||||
>
|
type="button"
|
||||||
{label.replace('Claude ', '')}
|
onClick={() => handleProviderChange('cursor')}
|
||||||
</button>
|
className={cn(
|
||||||
))}
|
'flex-1 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
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Thinking Level */}
|
{/* Claude Model Selection */}
|
||||||
{supportsThinking && (
|
{formData.provider === 'claude' && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label className="flex items-center gap-2">
|
||||||
|
<Brain className="w-4 h-4 text-primary" />
|
||||||
|
Model
|
||||||
|
</Label>
|
||||||
|
<div className="flex gap-2 flex-wrap">
|
||||||
|
{CLAUDE_MODELS.map(({ id, label }) => (
|
||||||
|
<button
|
||||||
|
key={id}
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleModelChange(id)}
|
||||||
|
className={cn(
|
||||||
|
'flex-1 min-w-[100px] px-3 py-2 rounded-md border text-sm font-medium transition-colors',
|
||||||
|
formData.model === id
|
||||||
|
? 'bg-primary text-primary-foreground border-primary'
|
||||||
|
: 'bg-background hover:bg-accent border-border'
|
||||||
|
)}
|
||||||
|
data-testid={`model-select-${id}`}
|
||||||
|
>
|
||||||
|
{label.replace('Claude ', '')}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Cursor Model Selection */}
|
||||||
|
{formData.provider === 'cursor' && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label className="flex items-center gap-2">
|
||||||
|
<Terminal className="w-4 h-4 text-primary" />
|
||||||
|
Cursor Model
|
||||||
|
</Label>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
{Object.entries(CURSOR_MODEL_MAP).map(([id, config]) => (
|
||||||
|
<button
|
||||||
|
key={id}
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleCursorModelChange(id as CursorModelId)}
|
||||||
|
className={cn(
|
||||||
|
'w-full px-3 py-2 rounded-md border text-sm font-medium transition-colors flex items-center justify-between',
|
||||||
|
formData.cursorModel === id
|
||||||
|
? 'bg-primary text-primary-foreground border-primary'
|
||||||
|
: 'bg-background hover:bg-accent border-border'
|
||||||
|
)}
|
||||||
|
data-testid={`cursor-model-select-${id}`}
|
||||||
|
>
|
||||||
|
<span>{config.label}</span>
|
||||||
|
<div className="flex gap-1">
|
||||||
|
{config.hasThinking && (
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className={cn(
|
||||||
|
'text-xs',
|
||||||
|
formData.cursorModel === id
|
||||||
|
? 'border-primary-foreground/50 text-primary-foreground'
|
||||||
|
: 'border-amber-500/50 text-amber-600 dark:text-amber-400'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Thinking
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
<Badge
|
||||||
|
variant={config.tier === 'free' ? 'default' : 'secondary'}
|
||||||
|
className={cn(
|
||||||
|
'text-xs',
|
||||||
|
formData.cursorModel === id && 'bg-primary-foreground/20'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{config.tier}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{formData.cursorModel && cursorModelHasThinking(formData.cursorModel) && (
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
This model has built-in extended thinking capabilities.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Claude Thinking Level */}
|
||||||
|
{formData.provider === 'claude' && supportsThinking && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="flex items-center gap-2">
|
<Label className="flex items-center gap-2">
|
||||||
<Brain className="w-4 h-4 text-amber-500" />
|
<Brain className="w-4 h-4 text-amber-500" />
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { GripVertical, Lock, Pencil, Trash2, Brain } from 'lucide-react';
|
import { GripVertical, Lock, Pencil, Trash2, Brain, Bot, Terminal } from 'lucide-react';
|
||||||
import { useSortable } from '@dnd-kit/sortable';
|
import { useSortable } from '@dnd-kit/sortable';
|
||||||
import { CSS } from '@dnd-kit/utilities';
|
import { CSS } from '@dnd-kit/utilities';
|
||||||
import type { AIProfile } from '@/store/app-store';
|
import type { AIProfile } from '@automaker/types';
|
||||||
|
import { CURSOR_MODEL_MAP, profileHasThinking } from '@automaker/types';
|
||||||
import { PROFILE_ICONS } from '../constants';
|
import { PROFILE_ICONS } from '../constants';
|
||||||
|
|
||||||
interface SortableProfileCardProps {
|
interface SortableProfileCardProps {
|
||||||
@@ -68,12 +69,29 @@ export function SortableProfileCard({ profile, onEdit, onDelete }: SortableProfi
|
|||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-muted-foreground mt-0.5 line-clamp-2">{profile.description}</p>
|
<p className="text-sm text-muted-foreground mt-0.5 line-clamp-2">{profile.description}</p>
|
||||||
<div className="flex items-center gap-2 mt-2 flex-wrap">
|
<div className="flex items-center gap-2 mt-2 flex-wrap">
|
||||||
<span className="text-xs px-2 py-0.5 rounded-full border border-primary/30 text-primary bg-primary/10">
|
{/* Provider badge */}
|
||||||
{profile.model}
|
<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" />
|
||||||
|
) : (
|
||||||
|
<Bot className="w-3 h-3" />
|
||||||
|
)}
|
||||||
|
{profile.provider === 'cursor' ? 'Cursor' : 'Claude'}
|
||||||
</span>
|
</span>
|
||||||
{profile.thinkingLevel !== 'none' && (
|
|
||||||
|
{/* Model badge */}
|
||||||
|
<span className="text-xs px-2 py-0.5 rounded-full border border-primary/30 text-primary bg-primary/10">
|
||||||
|
{profile.provider === 'cursor'
|
||||||
|
? CURSOR_MODEL_MAP[profile.cursorModel || 'auto']?.label ||
|
||||||
|
profile.cursorModel ||
|
||||||
|
'auto'
|
||||||
|
: profile.model || 'sonnet'}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Thinking badge - works for both providers */}
|
||||||
|
{profileHasThinking(profile) && (
|
||||||
<span className="text-xs px-2 py-0.5 rounded-full border border-amber-500/30 text-amber-600 dark:text-amber-400 bg-amber-500/10">
|
<span className="text-xs px-2 py-0.5 rounded-full border border-amber-500/30 text-amber-600 dark:text-amber-400 bg-amber-500/10">
|
||||||
{profile.thinkingLevel}
|
{profile.provider === 'cursor' ? 'Thinking' : profile.thinkingLevel}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,48 @@
|
|||||||
import type { AgentModel, ModelProvider } from '@/store/app-store';
|
import type { AgentModel, ModelProvider, AIProfile } from '@automaker/types';
|
||||||
|
import { CURSOR_MODEL_MAP } from '@automaker/types';
|
||||||
|
|
||||||
// Helper to determine provider from model
|
// Helper to determine provider from model (legacy, always returns 'claude')
|
||||||
export function getProviderFromModel(model: AgentModel): ModelProvider {
|
export function getProviderFromModel(model: AgentModel): ModelProvider {
|
||||||
return 'claude';
|
return 'claude';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate an AI profile for completeness and correctness
|
||||||
|
*/
|
||||||
|
export function validateProfile(profile: Partial<AIProfile>): {
|
||||||
|
valid: boolean;
|
||||||
|
errors: string[];
|
||||||
|
} {
|
||||||
|
const errors: string[] = [];
|
||||||
|
|
||||||
|
// Name is required
|
||||||
|
if (!profile.name?.trim()) {
|
||||||
|
errors.push('Profile name is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provider must be valid
|
||||||
|
if (!profile.provider || !['claude', 'cursor'].includes(profile.provider)) {
|
||||||
|
errors.push('Invalid provider');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Claude-specific validation
|
||||||
|
if (profile.provider === 'claude') {
|
||||||
|
if (!profile.model) {
|
||||||
|
errors.push('Claude model is required');
|
||||||
|
} else if (!['haiku', 'sonnet', 'opus'].includes(profile.model)) {
|
||||||
|
errors.push('Invalid Claude model');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cursor-specific validation
|
||||||
|
if (profile.provider === 'cursor') {
|
||||||
|
if (profile.cursorModel && !(profile.cursorModel in CURSOR_MODEL_MAP)) {
|
||||||
|
errors.push('Invalid Cursor model');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
valid: errors.length === 0,
|
||||||
|
errors,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -859,6 +859,7 @@ export interface AppActions {
|
|||||||
|
|
||||||
// Default built-in AI profiles
|
// Default built-in AI profiles
|
||||||
const DEFAULT_AI_PROFILES: AIProfile[] = [
|
const DEFAULT_AI_PROFILES: AIProfile[] = [
|
||||||
|
// Claude profiles
|
||||||
{
|
{
|
||||||
id: 'profile-heavy-task',
|
id: 'profile-heavy-task',
|
||||||
name: 'Heavy Task',
|
name: 'Heavy Task',
|
||||||
@@ -890,6 +891,34 @@ const DEFAULT_AI_PROFILES: AIProfile[] = [
|
|||||||
isBuiltIn: true,
|
isBuiltIn: true,
|
||||||
icon: 'Zap',
|
icon: 'Zap',
|
||||||
},
|
},
|
||||||
|
// Cursor profiles
|
||||||
|
{
|
||||||
|
id: 'profile-cursor-auto',
|
||||||
|
name: 'Cursor Auto',
|
||||||
|
description: 'Let Cursor choose the best model automatically.',
|
||||||
|
provider: 'cursor',
|
||||||
|
cursorModel: 'auto',
|
||||||
|
isBuiltIn: true,
|
||||||
|
icon: 'Sparkles',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'profile-cursor-fast',
|
||||||
|
name: 'Cursor Fast',
|
||||||
|
description: 'Quick responses with GPT-4o Mini via Cursor.',
|
||||||
|
provider: 'cursor',
|
||||||
|
cursorModel: 'gpt-4o-mini',
|
||||||
|
isBuiltIn: true,
|
||||||
|
icon: 'Zap',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'profile-cursor-thinking',
|
||||||
|
name: 'Cursor Thinking',
|
||||||
|
description: 'Claude Sonnet 4 with extended thinking via Cursor for complex tasks.',
|
||||||
|
provider: 'cursor',
|
||||||
|
cursorModel: 'claude-sonnet-4-thinking',
|
||||||
|
isBuiltIn: true,
|
||||||
|
icon: 'Brain',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const initialState: AppState = {
|
const initialState: AppState = {
|
||||||
|
|||||||
@@ -71,6 +71,8 @@ export {
|
|||||||
SETTINGS_VERSION,
|
SETTINGS_VERSION,
|
||||||
CREDENTIALS_VERSION,
|
CREDENTIALS_VERSION,
|
||||||
PROJECT_SETTINGS_VERSION,
|
PROJECT_SETTINGS_VERSION,
|
||||||
|
profileHasThinking,
|
||||||
|
getProfileModelString,
|
||||||
} from './settings.js';
|
} from './settings.js';
|
||||||
|
|
||||||
// Model display constants
|
// Model display constants
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { AgentModel } from './model.js';
|
import type { AgentModel } from './model.js';
|
||||||
|
import type { CursorModelId } from './cursor-models.js';
|
||||||
|
import { CURSOR_MODEL_MAP } from './cursor-models.js';
|
||||||
|
|
||||||
// Re-export AgentModel for convenience
|
// Re-export AgentModel for convenience
|
||||||
export type { AgentModel };
|
export type { AgentModel };
|
||||||
@@ -151,16 +153,54 @@ export interface AIProfile {
|
|||||||
name: string;
|
name: string;
|
||||||
/** User-friendly description */
|
/** User-friendly description */
|
||||||
description: string;
|
description: string;
|
||||||
/** Which Claude model to use (opus, sonnet, haiku) */
|
/** Provider selection: 'claude' or 'cursor' */
|
||||||
model: AgentModel;
|
|
||||||
/** Extended thinking level for reasoning-based tasks */
|
|
||||||
thinkingLevel: ThinkingLevel;
|
|
||||||
/** Provider (currently only "claude") */
|
|
||||||
provider: ModelProvider;
|
provider: ModelProvider;
|
||||||
/** Whether this is a built-in default profile */
|
/** Whether this is a built-in default profile */
|
||||||
isBuiltIn: boolean;
|
isBuiltIn: boolean;
|
||||||
/** Optional icon identifier or emoji */
|
/** Optional icon identifier or emoji */
|
||||||
icon?: string;
|
icon?: string;
|
||||||
|
|
||||||
|
// Claude-specific settings
|
||||||
|
/** Which Claude model to use (opus, sonnet, haiku) - only for Claude provider */
|
||||||
|
model?: AgentModel;
|
||||||
|
/** Extended thinking level for reasoning-based tasks - only for Claude provider */
|
||||||
|
thinkingLevel?: ThinkingLevel;
|
||||||
|
|
||||||
|
// Cursor-specific settings
|
||||||
|
/** Which Cursor model to use - only for Cursor provider
|
||||||
|
* Note: For Cursor, thinking is embedded in the model ID (e.g., 'claude-sonnet-4-thinking')
|
||||||
|
*/
|
||||||
|
cursorModel?: CursorModelId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to determine if a profile uses thinking mode
|
||||||
|
*/
|
||||||
|
export function profileHasThinking(profile: AIProfile): boolean {
|
||||||
|
if (profile.provider === 'claude') {
|
||||||
|
return profile.thinkingLevel !== undefined && profile.thinkingLevel !== 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profile.provider === 'cursor') {
|
||||||
|
const model = profile.cursorModel || 'auto';
|
||||||
|
// Check using model map for hasThinking flag, or check for 'thinking' in name
|
||||||
|
const modelConfig = CURSOR_MODEL_MAP[model];
|
||||||
|
return modelConfig?.hasThinking ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get effective model string for execution
|
||||||
|
*/
|
||||||
|
export function getProfileModelString(profile: AIProfile): string {
|
||||||
|
if (profile.provider === 'cursor') {
|
||||||
|
return `cursor:${profile.cursorModel || 'auto'}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Claude
|
||||||
|
return profile.model || 'sonnet';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
| 5 | [Log Parser Integration](phases/phase-5-log-parser.md) | `completed` | ✅ |
|
| 5 | [Log Parser Integration](phases/phase-5-log-parser.md) | `completed` | ✅ |
|
||||||
| 6 | [UI Setup Wizard](phases/phase-6-setup-wizard.md) | `completed` | ✅ |
|
| 6 | [UI Setup Wizard](phases/phase-6-setup-wizard.md) | `completed` | ✅ |
|
||||||
| 7 | [Settings View Provider Tabs](phases/phase-7-settings.md) | `completed` | ✅ |
|
| 7 | [Settings View Provider Tabs](phases/phase-7-settings.md) | `completed` | ✅ |
|
||||||
| 8 | [AI Profiles Integration](phases/phase-8-profiles.md) | `pending` | - |
|
| 8 | [AI Profiles Integration](phases/phase-8-profiles.md) | `completed` | ✅ |
|
||||||
| 9 | [Task Execution Integration](phases/phase-9-execution.md) | `pending` | - |
|
| 9 | [Task Execution Integration](phases/phase-9-execution.md) | `pending` | - |
|
||||||
| 10 | [Testing & Validation](phases/phase-10-testing.md) | `pending` | - |
|
| 10 | [Testing & Validation](phases/phase-10-testing.md) | `pending` | - |
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Phase 8: AI Profiles Integration
|
# Phase 8: AI Profiles Integration
|
||||||
|
|
||||||
**Status:** `pending`
|
**Status:** `completed`
|
||||||
**Dependencies:** Phase 1 (Types), Phase 7 (Settings)
|
**Dependencies:** Phase 1 (Types), Phase 7 (Settings)
|
||||||
**Estimated Effort:** Medium (UI + types)
|
**Estimated Effort:** Medium (UI + types)
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ Extend the AI Profiles system to support Cursor as a provider, with proper handl
|
|||||||
|
|
||||||
### Task 8.1: Update AIProfile Type
|
### Task 8.1: Update AIProfile Type
|
||||||
|
|
||||||
**Status:** `pending`
|
**Status:** `completed`
|
||||||
|
|
||||||
**File:** `libs/types/src/settings.ts`
|
**File:** `libs/types/src/settings.ts`
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ export function getProfileModelString(profile: AIProfile): string {
|
|||||||
|
|
||||||
### Task 8.2: Update Profile Form Component
|
### Task 8.2: Update Profile Form Component
|
||||||
|
|
||||||
**Status:** `pending`
|
**Status:** `completed`
|
||||||
|
|
||||||
**File:** `apps/ui/src/components/views/profiles-view/components/profile-form.tsx`
|
**File:** `apps/ui/src/components/views/profiles-view/components/profile-form.tsx`
|
||||||
|
|
||||||
@@ -289,7 +289,7 @@ export function ProfileForm({ profile, onSave, onCancel }: ProfileFormProps) {
|
|||||||
|
|
||||||
### Task 8.3: Update Profile Card Display
|
### Task 8.3: Update Profile Card Display
|
||||||
|
|
||||||
**Status:** `pending`
|
**Status:** `completed`
|
||||||
|
|
||||||
**File:** `apps/ui/src/components/views/profiles-view/components/profile-card.tsx`
|
**File:** `apps/ui/src/components/views/profiles-view/components/profile-card.tsx`
|
||||||
|
|
||||||
@@ -374,7 +374,7 @@ export function ProfileCard({ profile, onEdit, onDelete }: ProfileCardProps) {
|
|||||||
|
|
||||||
### Task 8.4: Add Default Cursor Profiles
|
### Task 8.4: Add Default Cursor Profiles
|
||||||
|
|
||||||
**Status:** `pending`
|
**Status:** `completed`
|
||||||
|
|
||||||
**File:** `apps/ui/src/components/views/profiles-view/constants.ts`
|
**File:** `apps/ui/src/components/views/profiles-view/constants.ts`
|
||||||
|
|
||||||
@@ -430,7 +430,7 @@ export const DEFAULT_PROFILES: AIProfile[] = [
|
|||||||
|
|
||||||
### Task 8.5: Update Profile Validation
|
### Task 8.5: Update Profile Validation
|
||||||
|
|
||||||
**Status:** `pending`
|
**Status:** `completed`
|
||||||
|
|
||||||
Add validation for profile data:
|
Add validation for profile data:
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user