import { useState, useEffect, useMemo } from 'react'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Checkbox } from '@/components/ui/checkbox'; import { Label } from '@/components/ui/label'; import { AlertCircle } from 'lucide-react'; import { modelSupportsThinking } from '@/lib/utils'; import { Feature, ModelAlias, ThinkingLevel, AIProfile, PlanningMode } from '@/store/app-store'; 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 { open: boolean; onClose: () => void; selectedFeatures: Feature[]; onApply: (updates: Partial) => Promise; showProfilesOnly: boolean; aiProfiles: AIProfile[]; } interface ApplyState { model: boolean; thinkingLevel: boolean; planningMode: boolean; requirePlanApproval: boolean; priority: boolean; skipTests: boolean; } function getMixedValues(features: Feature[]): Record { if (features.length === 0) return {}; const first = features[0]; return { model: !features.every((f) => f.model === first.model), thinkingLevel: !features.every((f) => f.thinkingLevel === first.thinkingLevel), planningMode: !features.every((f) => f.planningMode === first.planningMode), requirePlanApproval: !features.every( (f) => f.requirePlanApproval === first.requirePlanApproval ), priority: !features.every((f) => f.priority === first.priority), skipTests: !features.every((f) => f.skipTests === first.skipTests), }; } function getInitialValue(features: Feature[], key: keyof Feature, defaultValue: T): T { if (features.length === 0) return defaultValue; return (features[0][key] as T) ?? defaultValue; } interface FieldWrapperProps { label: string; isMixed: boolean; willApply: boolean; onApplyChange: (apply: boolean) => void; children: React.ReactNode; } function FieldWrapper({ label, isMixed, willApply, onApplyChange, children }: FieldWrapperProps) { return (
onApplyChange(!!checked)} className="data-[state=checked]:bg-brand-500 data-[state=checked]:border-brand-500" />
{isMixed && ( Mixed values )}
{children}
); } export function MassEditDialog({ open, onClose, selectedFeatures, onApply, showProfilesOnly, aiProfiles, }: MassEditDialogProps) { const [isApplying, setIsApplying] = useState(false); // Track which fields to apply const [applyState, setApplyState] = useState({ model: false, thinkingLevel: false, planningMode: false, requirePlanApproval: false, priority: false, skipTests: false, }); // Field values const [model, setModel] = useState('sonnet'); const [thinkingLevel, setThinkingLevel] = useState('none'); const [planningMode, setPlanningMode] = useState('skip'); const [requirePlanApproval, setRequirePlanApproval] = useState(false); const [priority, setPriority] = useState(2); const [skipTests, setSkipTests] = useState(false); // Calculate mixed values const mixedValues = useMemo(() => getMixedValues(selectedFeatures), [selectedFeatures]); // Reset state when dialog opens with new features useEffect(() => { if (open && selectedFeatures.length > 0) { setApplyState({ model: false, thinkingLevel: false, planningMode: false, requirePlanApproval: false, priority: false, skipTests: false, }); setModel(getInitialValue(selectedFeatures, 'model', 'sonnet') as ModelAlias); setThinkingLevel(getInitialValue(selectedFeatures, 'thinkingLevel', 'none') as ThinkingLevel); setPlanningMode(getInitialValue(selectedFeatures, 'planningMode', 'skip') as PlanningMode); setRequirePlanApproval(getInitialValue(selectedFeatures, 'requirePlanApproval', false)); setPriority(getInitialValue(selectedFeatures, 'priority', 2)); setSkipTests(getInitialValue(selectedFeatures, 'skipTests', false)); } }, [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 = {}; if (applyState.model) updates.model = model; if (applyState.thinkingLevel) updates.thinkingLevel = thinkingLevel; if (applyState.planningMode) updates.planningMode = planningMode; if (applyState.requirePlanApproval) updates.requirePlanApproval = requirePlanApproval; if (applyState.priority) updates.priority = priority; if (applyState.skipTests) updates.skipTests = skipTests; if (Object.keys(updates).length === 0) { onClose(); return; } setIsApplying(true); try { await onApply(updates); onClose(); } finally { setIsApplying(false); } }; const hasAnyApply = Object.values(applyState).some(Boolean); const isCurrentModelCursor = isCursorModel(model); const modelAllowsThinking = !isCurrentModelCursor && modelSupportsThinking(model); return ( !open && onClose()}> Edit {selectedFeatures.length} Features Select which settings to apply to all selected features.
{/* Quick Select Profile Section */} {aiProfiles.length > 0 && (

Selecting a profile will automatically enable model settings

)} {/* Model Selector */}

Or select a specific model configuration

{ 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 />
{/* Separator */}
{/* Planning Mode */} setApplyState((prev) => ({ ...prev, planningMode: apply, requirePlanApproval: apply, })) } > { setPlanningMode(newMode); // Auto-suggest approval based on mode, but user can override setRequirePlanApproval(newMode === 'spec' || newMode === 'full'); }} requireApproval={requirePlanApproval} onRequireApprovalChange={setRequirePlanApproval} testIdPrefix="mass-edit-planning" /> {/* Priority */} setApplyState((prev) => ({ ...prev, priority: apply }))} > {/* Testing */} setApplyState((prev) => ({ ...prev, skipTests: apply }))} >
); }