feat: add default AI profile selection to settings view

- Introduced default AI profile management in the settings view, allowing users to select a default profile for new features.
- Updated the Add Feature dialog to utilize the selected AI profile, setting default model and thinking level based on the chosen profile.
- Enhanced the Feature Defaults section to display and manage the default AI profile, including a dropdown for selection and relevant information display.
This commit is contained in:
Kacper
2025-12-20 01:51:46 +01:00
parent d104a24446
commit 80cf932ea4
4 changed files with 77 additions and 1 deletions

View File

@@ -126,16 +126,25 @@ export function AddFeatureDialog({
enhancementModel, enhancementModel,
defaultPlanningMode, defaultPlanningMode,
defaultRequirePlanApproval, defaultRequirePlanApproval,
defaultAIProfileId,
useWorktrees, useWorktrees,
} = useAppStore(); } = useAppStore();
// Sync defaults when dialog opens // Sync defaults when dialog opens
useEffect(() => { useEffect(() => {
if (open) { if (open) {
// Find the default profile if one is set
const defaultProfile = defaultAIProfileId
? aiProfiles.find((p) => p.id === defaultAIProfileId)
: null;
setNewFeature((prev) => ({ setNewFeature((prev) => ({
...prev, ...prev,
skipTests: defaultSkipTests, skipTests: defaultSkipTests,
branchName: defaultBranch || "", branchName: defaultBranch || "",
// Use default profile's model/thinkingLevel if set, else fallback to defaults
model: defaultProfile?.model ?? "opus",
thinkingLevel: defaultProfile?.thinkingLevel ?? "none",
})); }));
setUseCurrentBranch(true); setUseCurrentBranch(true);
setPlanningMode(defaultPlanningMode); setPlanningMode(defaultPlanningMode);
@@ -147,6 +156,8 @@ export function AddFeatureDialog({
defaultBranch, defaultBranch,
defaultPlanningMode, defaultPlanningMode,
defaultRequirePlanApproval, defaultRequirePlanApproval,
defaultAIProfileId,
aiProfiles,
]); ]);
const handleAdd = () => { const handleAdd = () => {

View File

@@ -43,6 +43,9 @@ export function SettingsView() {
setDefaultPlanningMode, setDefaultPlanningMode,
defaultRequirePlanApproval, defaultRequirePlanApproval,
setDefaultRequirePlanApproval, setDefaultRequirePlanApproval,
defaultAIProfileId,
setDefaultAIProfileId,
aiProfiles,
} = useAppStore(); } = useAppStore();
// Convert electron Project to settings-view Project type // Convert electron Project to settings-view Project type
@@ -127,12 +130,15 @@ export function SettingsView() {
useWorktrees={useWorktrees} useWorktrees={useWorktrees}
defaultPlanningMode={defaultPlanningMode} defaultPlanningMode={defaultPlanningMode}
defaultRequirePlanApproval={defaultRequirePlanApproval} defaultRequirePlanApproval={defaultRequirePlanApproval}
defaultAIProfileId={defaultAIProfileId}
aiProfiles={aiProfiles}
onShowProfilesOnlyChange={setShowProfilesOnly} onShowProfilesOnlyChange={setShowProfilesOnly}
onDefaultSkipTestsChange={setDefaultSkipTests} onDefaultSkipTestsChange={setDefaultSkipTests}
onEnableDependencyBlockingChange={setEnableDependencyBlocking} onEnableDependencyBlockingChange={setEnableDependencyBlocking}
onUseWorktreesChange={setUseWorktrees} onUseWorktreesChange={setUseWorktrees}
onDefaultPlanningModeChange={setDefaultPlanningMode} onDefaultPlanningModeChange={setDefaultPlanningMode}
onDefaultRequirePlanApprovalChange={setDefaultRequirePlanApproval} onDefaultRequirePlanApprovalChange={setDefaultRequirePlanApproval}
onDefaultAIProfileIdChange={setDefaultAIProfileId}
/> />
); );
case "danger": case "danger":

View File

@@ -2,7 +2,7 @@ import { Label } from "@/components/ui/label";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { import {
FlaskConical, Settings2, TestTube, GitBranch, AlertCircle, FlaskConical, Settings2, TestTube, GitBranch, AlertCircle,
Zap, ClipboardList, FileText, ScrollText, ShieldCheck Zap, ClipboardList, FileText, ScrollText, ShieldCheck, User
} from "lucide-react"; } from "lucide-react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { import {
@@ -12,6 +12,7 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import type { AIProfile } from "@/store/app-store";
type PlanningMode = 'skip' | 'lite' | 'spec' | 'full'; type PlanningMode = 'skip' | 'lite' | 'spec' | 'full';
@@ -22,12 +23,15 @@ interface FeatureDefaultsSectionProps {
useWorktrees: boolean; useWorktrees: boolean;
defaultPlanningMode: PlanningMode; defaultPlanningMode: PlanningMode;
defaultRequirePlanApproval: boolean; defaultRequirePlanApproval: boolean;
defaultAIProfileId: string | null;
aiProfiles: AIProfile[];
onShowProfilesOnlyChange: (value: boolean) => void; onShowProfilesOnlyChange: (value: boolean) => void;
onDefaultSkipTestsChange: (value: boolean) => void; onDefaultSkipTestsChange: (value: boolean) => void;
onEnableDependencyBlockingChange: (value: boolean) => void; onEnableDependencyBlockingChange: (value: boolean) => void;
onUseWorktreesChange: (value: boolean) => void; onUseWorktreesChange: (value: boolean) => void;
onDefaultPlanningModeChange: (value: PlanningMode) => void; onDefaultPlanningModeChange: (value: PlanningMode) => void;
onDefaultRequirePlanApprovalChange: (value: boolean) => void; onDefaultRequirePlanApprovalChange: (value: boolean) => void;
onDefaultAIProfileIdChange: (value: string | null) => void;
} }
export function FeatureDefaultsSection({ export function FeatureDefaultsSection({
@@ -37,13 +41,20 @@ export function FeatureDefaultsSection({
useWorktrees, useWorktrees,
defaultPlanningMode, defaultPlanningMode,
defaultRequirePlanApproval, defaultRequirePlanApproval,
defaultAIProfileId,
aiProfiles,
onShowProfilesOnlyChange, onShowProfilesOnlyChange,
onDefaultSkipTestsChange, onDefaultSkipTestsChange,
onEnableDependencyBlockingChange, onEnableDependencyBlockingChange,
onUseWorktreesChange, onUseWorktreesChange,
onDefaultPlanningModeChange, onDefaultPlanningModeChange,
onDefaultRequirePlanApprovalChange, onDefaultRequirePlanApprovalChange,
onDefaultAIProfileIdChange,
}: FeatureDefaultsSectionProps) { }: FeatureDefaultsSectionProps) {
// Find the selected profile name for display
const selectedProfile = defaultAIProfileId
? aiProfiles.find((p) => p.id === defaultAIProfileId)
: null;
return ( return (
<div <div
className={cn( className={cn(
@@ -169,6 +180,49 @@ export function FeatureDefaultsSection({
{/* Separator */} {/* Separator */}
{defaultPlanningMode === 'skip' && <div className="border-t border-border/30" />} {defaultPlanningMode === 'skip' && <div className="border-t border-border/30" />}
{/* Default AI Profile */}
<div className="group flex items-start space-x-3 p-3 rounded-xl hover:bg-accent/30 transition-colors duration-200 -mx-3">
<div className="w-10 h-10 mt-0.5 rounded-xl flex items-center justify-center shrink-0 bg-brand-500/10">
<User className="w-5 h-5 text-brand-500" />
</div>
<div className="flex-1 space-y-2">
<div className="flex items-center justify-between">
<Label className="text-foreground font-medium">
Default AI Profile
</Label>
<Select
value={defaultAIProfileId ?? "none"}
onValueChange={(v: string) => onDefaultAIProfileIdChange(v === "none" ? null : v)}
>
<SelectTrigger
className="w-[180px] h-8"
data-testid="default-ai-profile-select"
>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="none">
<span className="text-muted-foreground">None (pick manually)</span>
</SelectItem>
{aiProfiles.map((profile) => (
<SelectItem key={profile.id} value={profile.id}>
<span>{profile.name}</span>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<p className="text-xs text-muted-foreground/80 leading-relaxed">
{selectedProfile
? `New features will use the "${selectedProfile.name}" profile (${selectedProfile.model}, ${selectedProfile.thinkingLevel} thinking).`
: "Pre-select an AI profile when creating new features. Choose \"None\" to pick manually each time."}
</p>
</div>
</div>
{/* Separator */}
<div className="border-t border-border/30" />
{/* Profiles Only Setting */} {/* Profiles Only Setting */}
<div className="group flex items-start space-x-3 p-3 rounded-xl hover:bg-accent/30 transition-colors duration-200 -mx-3"> <div className="group flex items-start space-x-3 p-3 rounded-xl hover:bg-accent/30 transition-colors duration-200 -mx-3">
<Checkbox <Checkbox

View File

@@ -494,6 +494,7 @@ export interface AppState {
defaultPlanningMode: PlanningMode; defaultPlanningMode: PlanningMode;
defaultRequirePlanApproval: boolean; defaultRequirePlanApproval: boolean;
defaultAIProfileId: string | null;
// Plan Approval State // Plan Approval State
// When a plan requires user approval, this holds the pending approval details // When a plan requires user approval, this holds the pending approval details
@@ -742,6 +743,7 @@ export interface AppActions {
setDefaultPlanningMode: (mode: PlanningMode) => void; setDefaultPlanningMode: (mode: PlanningMode) => void;
setDefaultRequirePlanApproval: (require: boolean) => void; setDefaultRequirePlanApproval: (require: boolean) => void;
setDefaultAIProfileId: (profileId: string | null) => void;
// Plan Approval actions // Plan Approval actions
setPendingPlanApproval: (approval: { setPendingPlanApproval: (approval: {
@@ -841,6 +843,7 @@ const initialState: AppState = {
specCreatingForProject: null, specCreatingForProject: null,
defaultPlanningMode: 'skip' as PlanningMode, defaultPlanningMode: 'skip' as PlanningMode,
defaultRequirePlanApproval: false, defaultRequirePlanApproval: false,
defaultAIProfileId: null,
pendingPlanApproval: null, pendingPlanApproval: null,
}; };
@@ -2265,6 +2268,7 @@ export const useAppStore = create<AppState & AppActions>()(
setDefaultPlanningMode: (mode) => set({ defaultPlanningMode: mode }), setDefaultPlanningMode: (mode) => set({ defaultPlanningMode: mode }),
setDefaultRequirePlanApproval: (require) => set({ defaultRequirePlanApproval: require }), setDefaultRequirePlanApproval: (require) => set({ defaultRequirePlanApproval: require }),
setDefaultAIProfileId: (profileId) => set({ defaultAIProfileId: profileId }),
// Plan Approval actions // Plan Approval actions
setPendingPlanApproval: (approval) => set({ pendingPlanApproval: approval }), setPendingPlanApproval: (approval) => set({ pendingPlanApproval: approval }),
@@ -2340,6 +2344,7 @@ export const useAppStore = create<AppState & AppActions>()(
boardBackgroundByProject: state.boardBackgroundByProject, boardBackgroundByProject: state.boardBackgroundByProject,
defaultPlanningMode: state.defaultPlanningMode, defaultPlanningMode: state.defaultPlanningMode,
defaultRequirePlanApproval: state.defaultRequirePlanApproval, defaultRequirePlanApproval: state.defaultRequirePlanApproval,
defaultAIProfileId: state.defaultAIProfileId,
}), }),
} }
) )