mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-01 20:23:36 +00:00
refactor: update mass edit dialog and introduce new select components
- Removed advanced options toggle and related state from the mass edit dialog for a cleaner UI. - Replaced ProfileQuickSelect with ProfileSelect for better profile management. - Introduced new PlanningModeSelect and PrioritySelect components for streamlined selection of planning modes and priorities. - Updated imports in shared index to include new select components. - Enhanced the mass edit dialog to utilize the new components, improving user experience during bulk edits.
This commit is contained in:
@@ -10,18 +10,12 @@ import {
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Settings2, AlertCircle } from 'lucide-react';
|
||||
import { AlertCircle } from 'lucide-react';
|
||||
import { modelSupportsThinking } from '@/lib/utils';
|
||||
import { Feature, ModelAlias, ThinkingLevel, AIProfile, PlanningMode } from '@/store/app-store';
|
||||
import {
|
||||
ModelSelector,
|
||||
ThinkingLevelSelector,
|
||||
ProfileQuickSelect,
|
||||
TestingTabContent,
|
||||
PrioritySelector,
|
||||
PlanningModeSelector,
|
||||
} from '../shared';
|
||||
import { isCursorModel, PROVIDER_PREFIXES } from '@automaker/types';
|
||||
import { ProfileSelect, TestingTabContent, PrioritySelect, PlanningModeSelect } from '../shared';
|
||||
import { PhaseModelSelector } from '@/components/views/settings-view/model-defaults/phase-model-selector';
|
||||
import { isCursorModel, PROVIDER_PREFIXES, type PhaseModelEntry } from '@automaker/types';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface MassEditDialogProps {
|
||||
@@ -113,7 +107,6 @@ export function MassEditDialog({
|
||||
aiProfiles,
|
||||
}: MassEditDialogProps) {
|
||||
const [isApplying, setIsApplying] = useState(false);
|
||||
const [showAdvancedOptions, setShowAdvancedOptions] = useState(false);
|
||||
|
||||
// Track which fields to apply
|
||||
const [applyState, setApplyState] = useState<ApplyState>({
|
||||
@@ -153,7 +146,6 @@ export function MassEditDialog({
|
||||
setRequirePlanApproval(getInitialValue(selectedFeatures, 'requirePlanApproval', false));
|
||||
setPriority(getInitialValue(selectedFeatures, 'priority', 2));
|
||||
setSkipTests(getInitialValue(selectedFeatures, 'skipTests', false));
|
||||
setShowAdvancedOptions(false);
|
||||
}
|
||||
}, [open, selectedFeatures]);
|
||||
|
||||
@@ -216,27 +208,6 @@ export function MassEditDialog({
|
||||
</DialogHeader>
|
||||
|
||||
<div className="py-4 pr-4 space-y-4 max-h-[60vh] overflow-y-auto">
|
||||
{/* Show Advanced Options Toggle */}
|
||||
{showProfilesOnly && (
|
||||
<div className="flex items-center justify-between p-3 bg-muted/30 rounded-lg border border-border">
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium text-foreground">Simple Mode Active</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Only showing AI profiles. Advanced model tweaking is hidden.
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setShowAdvancedOptions(!showAdvancedOptions)}
|
||||
data-testid="mass-edit-show-advanced-toggle"
|
||||
>
|
||||
<Settings2 className="w-4 h-4 mr-2" />
|
||||
{showAdvancedOptions ? 'Hide' : 'Show'} Advanced
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Quick Select Profile Section */}
|
||||
{aiProfiles.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
@@ -244,7 +215,7 @@ export function MassEditDialog({
|
||||
<p className="text-xs text-muted-foreground mb-2">
|
||||
Selecting a profile will automatically enable model settings
|
||||
</p>
|
||||
<ProfileQuickSelect
|
||||
<ProfileSelect
|
||||
profiles={aiProfiles}
|
||||
selectedModel={model}
|
||||
selectedThinkingLevel={thinkingLevel}
|
||||
@@ -255,48 +226,30 @@ export function MassEditDialog({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Model Selector */}
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm font-medium">AI Model</Label>
|
||||
<p className="text-xs text-muted-foreground mb-2">
|
||||
Or select a specific model configuration
|
||||
</p>
|
||||
<PhaseModelSelector
|
||||
value={{ model, thinkingLevel }}
|
||||
onChange={(entry: PhaseModelEntry) => {
|
||||
setModel(entry.model as ModelAlias);
|
||||
setThinkingLevel(entry.thinkingLevel || 'none');
|
||||
// Auto-enable model and thinking level for apply state
|
||||
setApplyState((prev) => ({
|
||||
...prev,
|
||||
model: true,
|
||||
thinkingLevel: true,
|
||||
}));
|
||||
}}
|
||||
compact
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Separator */}
|
||||
{aiProfiles.length > 0 && (!showProfilesOnly || showAdvancedOptions) && (
|
||||
<div className="border-t border-border" />
|
||||
)}
|
||||
|
||||
{/* Model Selection */}
|
||||
{(!showProfilesOnly || showAdvancedOptions) && (
|
||||
<>
|
||||
<FieldWrapper
|
||||
label="AI Model"
|
||||
isMixed={mixedValues.model}
|
||||
willApply={applyState.model}
|
||||
onApplyChange={(apply) => setApplyState((prev) => ({ ...prev, model: apply }))}
|
||||
>
|
||||
<ModelSelector
|
||||
selectedModel={model}
|
||||
onModelSelect={handleModelSelect}
|
||||
testIdPrefix="mass-edit-model"
|
||||
/>
|
||||
</FieldWrapper>
|
||||
|
||||
{modelAllowsThinking && (
|
||||
<FieldWrapper
|
||||
label="Thinking Level"
|
||||
isMixed={mixedValues.thinkingLevel}
|
||||
willApply={applyState.thinkingLevel}
|
||||
onApplyChange={(apply) =>
|
||||
setApplyState((prev) => ({ ...prev, thinkingLevel: apply }))
|
||||
}
|
||||
>
|
||||
<ThinkingLevelSelector
|
||||
selectedLevel={thinkingLevel}
|
||||
onLevelSelect={setThinkingLevel}
|
||||
testIdPrefix="mass-edit-thinking"
|
||||
/>
|
||||
</FieldWrapper>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Separator before options */}
|
||||
{(!showProfilesOnly || showAdvancedOptions) && <div className="border-t border-border" />}
|
||||
<div className="border-t border-border" />
|
||||
|
||||
{/* Planning Mode */}
|
||||
<FieldWrapper
|
||||
@@ -311,14 +264,16 @@ export function MassEditDialog({
|
||||
}))
|
||||
}
|
||||
>
|
||||
<PlanningModeSelector
|
||||
<PlanningModeSelect
|
||||
mode={planningMode}
|
||||
onModeChange={setPlanningMode}
|
||||
onModeChange={(newMode) => {
|
||||
setPlanningMode(newMode);
|
||||
// Auto-suggest approval based on mode, but user can override
|
||||
setRequirePlanApproval(newMode === 'spec' || newMode === 'full');
|
||||
}}
|
||||
requireApproval={requirePlanApproval}
|
||||
onRequireApprovalChange={setRequirePlanApproval}
|
||||
featureDescription=""
|
||||
testIdPrefix="mass-edit"
|
||||
compact
|
||||
testIdPrefix="mass-edit-planning"
|
||||
/>
|
||||
</FieldWrapper>
|
||||
|
||||
@@ -329,7 +284,7 @@ export function MassEditDialog({
|
||||
willApply={applyState.priority}
|
||||
onApplyChange={(apply) => setApplyState((prev) => ({ ...prev, priority: apply }))}
|
||||
>
|
||||
<PrioritySelector
|
||||
<PrioritySelect
|
||||
selectedPriority={priority}
|
||||
onPrioritySelect={setPriority}
|
||||
testIdPrefix="mass-edit-priority"
|
||||
|
||||
@@ -2,8 +2,11 @@ export * from './model-constants';
|
||||
export * from './model-selector';
|
||||
export * from './thinking-level-selector';
|
||||
export * from './profile-quick-select';
|
||||
export * from './profile-select';
|
||||
export * from './testing-tab-content';
|
||||
export * from './priority-selector';
|
||||
export * from './priority-select';
|
||||
export * from './branch-selector';
|
||||
export * from './planning-mode-selector';
|
||||
export * from './planning-mode-select';
|
||||
export * from './ancestor-context-section';
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
import { Zap, ClipboardList, FileText, ScrollText } from 'lucide-react';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { PlanningMode } from '@automaker/types';
|
||||
|
||||
interface PlanningModeSelectProps {
|
||||
mode: PlanningMode;
|
||||
onModeChange: (mode: PlanningMode) => void;
|
||||
requireApproval?: boolean;
|
||||
onRequireApprovalChange?: (require: boolean) => void;
|
||||
testIdPrefix?: string;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const modes = [
|
||||
{
|
||||
value: 'skip' as const,
|
||||
label: 'Skip',
|
||||
description: 'Direct implementation, no upfront planning',
|
||||
icon: Zap,
|
||||
color: 'text-emerald-500',
|
||||
},
|
||||
{
|
||||
value: 'lite' as const,
|
||||
label: 'Lite',
|
||||
description: 'Think through approach, create task list',
|
||||
icon: ClipboardList,
|
||||
color: 'text-blue-500',
|
||||
},
|
||||
{
|
||||
value: 'spec' as const,
|
||||
label: 'Spec',
|
||||
description: 'Generate spec with acceptance criteria',
|
||||
icon: FileText,
|
||||
color: 'text-purple-500',
|
||||
},
|
||||
{
|
||||
value: 'full' as const,
|
||||
label: 'Full',
|
||||
description: 'Comprehensive spec with phased plan',
|
||||
icon: ScrollText,
|
||||
color: 'text-amber-500',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* PlanningModeSelect - Compact dropdown selector for planning modes
|
||||
*
|
||||
* A lightweight alternative to PlanningModeSelector for contexts where
|
||||
* spec management UI is not needed (e.g., mass edit, bulk operations).
|
||||
*
|
||||
* Shows icon + label in dropdown, with description text below.
|
||||
* Does not include spec generation, approval, or require-approval checkbox.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <PlanningModeSelect
|
||||
* mode={planningMode}
|
||||
* onModeChange={(mode) => {
|
||||
* setPlanningMode(mode);
|
||||
* setRequireApproval(mode === 'spec' || mode === 'full');
|
||||
* }}
|
||||
* testIdPrefix="mass-edit-planning"
|
||||
* />
|
||||
* ```
|
||||
*/
|
||||
export function PlanningModeSelect({
|
||||
mode,
|
||||
onModeChange,
|
||||
requireApproval,
|
||||
onRequireApprovalChange,
|
||||
testIdPrefix = 'planning-mode',
|
||||
className,
|
||||
disabled = false,
|
||||
}: PlanningModeSelectProps) {
|
||||
const selectedMode = modes.find((m) => m.value === mode);
|
||||
|
||||
// Disable approval checkbox for skip/lite modes since they don't use planning
|
||||
const isApprovalDisabled = disabled || mode === 'skip' || mode === 'lite';
|
||||
|
||||
return (
|
||||
<div className={cn('space-y-2', className)}>
|
||||
<Select
|
||||
value={mode}
|
||||
onValueChange={(value: string) => onModeChange(value as PlanningMode)}
|
||||
disabled={disabled}
|
||||
>
|
||||
<SelectTrigger className="h-9" data-testid={`${testIdPrefix}-select-trigger`}>
|
||||
<SelectValue>
|
||||
{selectedMode && (
|
||||
<div className="flex items-center gap-2">
|
||||
<selectedMode.icon className={cn('h-4 w-4', selectedMode.color)} />
|
||||
<span>{selectedMode.label}</span>
|
||||
</div>
|
||||
)}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{modes.map((m) => {
|
||||
const Icon = m.icon;
|
||||
return (
|
||||
<SelectItem
|
||||
key={m.value}
|
||||
value={m.value}
|
||||
data-testid={`${testIdPrefix}-option-${m.value}`}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Icon className={cn('h-3.5 w-3.5', m.color)} />
|
||||
<span>{m.label}</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{selectedMode && <p className="text-xs text-muted-foreground">{selectedMode.description}</p>}
|
||||
{onRequireApprovalChange && (
|
||||
<div className="flex items-center gap-2 pt-1">
|
||||
<Checkbox
|
||||
id={`${testIdPrefix}-require-approval`}
|
||||
checked={requireApproval && !isApprovalDisabled}
|
||||
onCheckedChange={(checked) => onRequireApprovalChange(!!checked)}
|
||||
disabled={isApprovalDisabled}
|
||||
data-testid={`${testIdPrefix}-require-approval-checkbox`}
|
||||
/>
|
||||
<Label
|
||||
htmlFor={`${testIdPrefix}-require-approval`}
|
||||
className={cn(
|
||||
'text-sm font-normal',
|
||||
isApprovalDisabled ? 'cursor-not-allowed text-muted-foreground' : 'cursor-pointer'
|
||||
)}
|
||||
>
|
||||
Require plan approval before execution
|
||||
</Label>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { AlertCircle, ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface PrioritySelectProps {
|
||||
selectedPriority: number;
|
||||
onPrioritySelect: (priority: number) => void;
|
||||
testIdPrefix?: string;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const priorities = [
|
||||
{
|
||||
value: 1,
|
||||
label: 'High',
|
||||
description: 'Urgent, needs immediate attention',
|
||||
icon: ChevronUp,
|
||||
color: 'text-red-500',
|
||||
bgColor: 'bg-red-500/10',
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
label: 'Medium',
|
||||
description: 'Normal priority, standard workflow',
|
||||
icon: AlertCircle,
|
||||
color: 'text-yellow-500',
|
||||
bgColor: 'bg-yellow-500/10',
|
||||
},
|
||||
{
|
||||
value: 3,
|
||||
label: 'Low',
|
||||
description: 'Can wait, not time-sensitive',
|
||||
icon: ChevronDown,
|
||||
color: 'text-blue-500',
|
||||
bgColor: 'bg-blue-500/10',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* PrioritySelect - Compact dropdown selector for feature priority
|
||||
*
|
||||
* A lightweight alternative to PrioritySelector for contexts where
|
||||
* space is limited (e.g., mass edit, bulk operations).
|
||||
*
|
||||
* Shows icon + priority level in dropdown, with description below.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <PrioritySelect
|
||||
* selectedPriority={priority}
|
||||
* onPrioritySelect={setPriority}
|
||||
* testIdPrefix="mass-edit-priority"
|
||||
* />
|
||||
* ```
|
||||
*/
|
||||
export function PrioritySelect({
|
||||
selectedPriority,
|
||||
onPrioritySelect,
|
||||
testIdPrefix = 'priority',
|
||||
className,
|
||||
disabled = false,
|
||||
}: PrioritySelectProps) {
|
||||
const selectedPriorityObj = priorities.find((p) => p.value === selectedPriority);
|
||||
|
||||
return (
|
||||
<div className={cn('space-y-2', className)}>
|
||||
<Select
|
||||
value={selectedPriority.toString()}
|
||||
onValueChange={(value: string) => onPrioritySelect(parseInt(value, 10))}
|
||||
disabled={disabled}
|
||||
>
|
||||
<SelectTrigger className="h-9" data-testid={`${testIdPrefix}-select-trigger`}>
|
||||
<SelectValue>
|
||||
{selectedPriorityObj && (
|
||||
<div className="flex items-center gap-2">
|
||||
<selectedPriorityObj.icon className={cn('h-4 w-4', selectedPriorityObj.color)} />
|
||||
<span>{selectedPriorityObj.label}</span>
|
||||
</div>
|
||||
)}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{priorities.map((p) => {
|
||||
const Icon = p.icon;
|
||||
return (
|
||||
<SelectItem
|
||||
key={p.value}
|
||||
value={p.value.toString()}
|
||||
data-testid={`${testIdPrefix}-option-${p.label.toLowerCase()}`}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Icon className={cn('h-3.5 w-3.5', p.color)} />
|
||||
<span>{p.label}</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{selectedPriorityObj && (
|
||||
<p className="text-xs text-muted-foreground">{selectedPriorityObj.description}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
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 } 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;
|
||||
}
|
||||
// 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 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
|
||||
* <ProfileSelect
|
||||
* profiles={aiProfiles}
|
||||
* selectedModel={model}
|
||||
* selectedThinkingLevel={thinkingLevel}
|
||||
* selectedCursorModel={isCurrentModelCursor ? model : undefined}
|
||||
* onSelect={handleProfileSelect}
|
||||
* testIdPrefix="mass-edit-profile"
|
||||
* />
|
||||
* ```
|
||||
*/
|
||||
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 (
|
||||
<div className={cn('space-y-2', className)}>
|
||||
<Select
|
||||
value={selectedProfile?.id || 'none'}
|
||||
onValueChange={(value: string) => {
|
||||
if (value !== 'none') {
|
||||
const profile = profiles.find((p) => p.id === value);
|
||||
if (profile) {
|
||||
onSelect(profile);
|
||||
}
|
||||
}
|
||||
}}
|
||||
disabled={disabled}
|
||||
>
|
||||
<SelectTrigger className="h-9" data-testid={`${testIdPrefix}-select-trigger`}>
|
||||
<SelectValue>
|
||||
{selectedProfile ? (
|
||||
<div className="flex items-center gap-2">
|
||||
{selectedProfile.provider === 'cursor' ? (
|
||||
<Terminal className="h-4 w-4 text-amber-500" />
|
||||
) : (
|
||||
(() => {
|
||||
const IconComponent = selectedProfile.icon
|
||||
? PROFILE_ICONS[selectedProfile.icon]
|
||||
: Brain;
|
||||
return <IconComponent className="h-4 w-4 text-primary" />;
|
||||
})()
|
||||
)}
|
||||
<span>{selectedProfile.name}</span>
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-muted-foreground">Select a profile...</span>
|
||||
)}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="none" className="text-muted-foreground">
|
||||
No profile selected
|
||||
</SelectItem>
|
||||
{profiles.map((profile) => {
|
||||
const isCursorProfile = profile.provider === 'cursor';
|
||||
const IconComponent = profile.icon ? PROFILE_ICONS[profile.icon] : Brain;
|
||||
|
||||
return (
|
||||
<SelectItem
|
||||
key={profile.id}
|
||||
value={profile.id}
|
||||
data-testid={`${testIdPrefix}-option-${profile.id}`}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{isCursorProfile ? (
|
||||
<Terminal className="h-3.5 w-3.5 text-amber-500" />
|
||||
) : (
|
||||
<IconComponent className="h-3.5 w-3.5 text-primary" />
|
||||
)}
|
||||
<div className="flex flex-col">
|
||||
<span className="text-sm">{profile.name}</span>
|
||||
<span className="text-[10px] text-muted-foreground">
|
||||
{getProfileModelDisplay(profile)}
|
||||
{getProfileThinkingDisplay(profile) &&
|
||||
` + ${getProfileThinkingDisplay(profile)}`}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{selectedProfile && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{getProfileModelDisplay(selectedProfile)}
|
||||
{getProfileThinkingDisplay(selectedProfile) &&
|
||||
` + ${getProfileThinkingDisplay(selectedProfile)}`}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user