mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-02 20:43:36 +00:00
feat: add per-project default model override for new features (#640)
* feat: add per-project default model override for new features
- Add defaultFeatureModel to ProjectSettings type for project-level override
- Add defaultFeatureModel to Project interface for UI state
- Display Default Feature Model in Model Defaults section alongside phase models
- Include Default Feature Model in global Bulk Replace dialog
- Add Default Feature Model override section to Project Settings
- Add setProjectDefaultFeatureModel store action for project-level overrides
- Update clearAllProjectPhaseModelOverrides to also clear defaultFeatureModel
- Update add-feature-dialog to use project override when available
- Include Default Feature Model in Project Bulk Replace dialog
This allows projects with different complexity levels to use different
default models (e.g., Haiku for simple tasks, Opus for complex projects).
* fix: add server-side __CLEAR__ handler for defaultFeatureModel
- Add handler in settings-service.ts to properly delete defaultFeatureModel
when '__CLEAR__' marker is sent from the UI
- Fix bulk-replace-dialog.tsx to correctly return claude-opus when resetting
default feature model to Anthropic Direct (was incorrectly using
enhancementModel's settings which default to sonnet)
These fixes ensure:
1. Clearing project default model override properly removes the setting
instead of storing literal '__CLEAR__' string
2. Global bulk replace correctly resets default feature model to opus
* fix: include defaultFeatureModel in Reset to Defaults action
- Updated resetPhaseModels to also reset defaultFeatureModel to claude-opus
- Fixed initial state to use canonical 'claude-opus' instead of 'opus'
* refactor: use DEFAULT_GLOBAL_SETTINGS constant for defaultFeatureModel
Address PR review feedback:
- Replace hardcoded { model: 'claude-opus' } with DEFAULT_GLOBAL_SETTINGS.defaultFeatureModel
- Fix Prettier formatting for long destructuring lines
- Import DEFAULT_GLOBAL_SETTINGS from @automaker/types where needed
This improves maintainability by centralizing the default value.
This commit is contained in:
committed by
GitHub
parent
3ebd67f35f
commit
5ab53afd7f
@@ -1,13 +1,13 @@
|
||||
import { useState } from 'react';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Workflow, RotateCcw, Globe, Check, Replace } from 'lucide-react';
|
||||
import { Workflow, RotateCcw, Globe, Check, Replace, Sparkles } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { Project } from '@/lib/electron';
|
||||
import { PhaseModelSelector } from '@/components/views/settings-view/model-defaults/phase-model-selector';
|
||||
import { ProjectBulkReplaceDialog } from './project-bulk-replace-dialog';
|
||||
import type { PhaseModelKey, PhaseModelEntry } from '@automaker/types';
|
||||
import { DEFAULT_PHASE_MODELS } from '@automaker/types';
|
||||
import { DEFAULT_PHASE_MODELS, DEFAULT_GLOBAL_SETTINGS } from '@automaker/types';
|
||||
|
||||
interface ProjectModelsSectionProps {
|
||||
project: Project;
|
||||
@@ -88,6 +88,127 @@ const MEMORY_TASKS: PhaseConfig[] = [
|
||||
|
||||
const ALL_PHASES = [...QUICK_TASKS, ...VALIDATION_TASKS, ...GENERATION_TASKS, ...MEMORY_TASKS];
|
||||
|
||||
/**
|
||||
* Default feature model override section for per-project settings.
|
||||
*/
|
||||
function FeatureDefaultModelOverrideSection({ project }: { project: Project }) {
|
||||
const {
|
||||
defaultFeatureModel: globalDefaultFeatureModel,
|
||||
setProjectDefaultFeatureModel,
|
||||
claudeCompatibleProviders,
|
||||
} = useAppStore();
|
||||
|
||||
const globalValue: PhaseModelEntry =
|
||||
globalDefaultFeatureModel ?? DEFAULT_GLOBAL_SETTINGS.defaultFeatureModel;
|
||||
const projectOverride = project.defaultFeatureModel;
|
||||
const hasOverride = !!projectOverride;
|
||||
const effectiveValue = projectOverride || globalValue;
|
||||
|
||||
// Get display name for a model
|
||||
const getModelDisplayName = (entry: PhaseModelEntry): string => {
|
||||
if (entry.providerId) {
|
||||
const provider = (claudeCompatibleProviders || []).find((p) => p.id === entry.providerId);
|
||||
if (provider) {
|
||||
const model = provider.models?.find((m) => m.id === entry.model);
|
||||
if (model) {
|
||||
return `${model.displayName} (${provider.name})`;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Default to model ID for built-in models (both short aliases and canonical IDs)
|
||||
const modelMap: Record<string, string> = {
|
||||
haiku: 'Claude Haiku',
|
||||
sonnet: 'Claude Sonnet',
|
||||
opus: 'Claude Opus',
|
||||
'claude-haiku': 'Claude Haiku',
|
||||
'claude-sonnet': 'Claude Sonnet',
|
||||
'claude-opus': 'Claude Opus',
|
||||
};
|
||||
return modelMap[entry.model] || entry.model;
|
||||
};
|
||||
|
||||
const handleClearOverride = () => {
|
||||
setProjectDefaultFeatureModel(project.id, null);
|
||||
};
|
||||
|
||||
const handleSetOverride = (entry: PhaseModelEntry) => {
|
||||
setProjectDefaultFeatureModel(project.id, entry);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-foreground">Feature Defaults</h3>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Default model for new feature cards in this project
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center justify-between p-4 rounded-xl',
|
||||
'bg-accent/20 border',
|
||||
hasOverride ? 'border-brand-500/30 bg-brand-500/5' : 'border-border/30',
|
||||
'hover:bg-accent/30 transition-colors'
|
||||
)}
|
||||
>
|
||||
<div className="flex-1 pr-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-8 h-8 rounded-lg bg-brand-500/10 flex items-center justify-center">
|
||||
<Sparkles className="w-4 h-4 text-brand-500" />
|
||||
</div>
|
||||
<h4 className="text-sm font-medium text-foreground">Default Feature Model</h4>
|
||||
{hasOverride ? (
|
||||
<span className="px-1.5 py-0.5 text-[10px] font-medium rounded bg-brand-500/20 text-brand-500">
|
||||
Override
|
||||
</span>
|
||||
) : (
|
||||
<span className="flex items-center gap-1 px-1.5 py-0.5 text-[10px] font-medium rounded bg-muted text-muted-foreground">
|
||||
<Globe className="w-3 h-3" />
|
||||
Global
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-1 ml-10">
|
||||
Model and thinking level used when creating new feature cards
|
||||
</p>
|
||||
{hasOverride && (
|
||||
<p className="text-xs text-brand-500 mt-1 ml-10">
|
||||
Using: {getModelDisplayName(effectiveValue)}
|
||||
</p>
|
||||
)}
|
||||
{!hasOverride && (
|
||||
<p className="text-xs text-muted-foreground/70 mt-1 ml-10">
|
||||
Using global: {getModelDisplayName(globalValue)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
{hasOverride && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleClearOverride}
|
||||
className="h-8 px-2 text-xs text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<RotateCcw className="w-3.5 h-3.5 mr-1" />
|
||||
Reset
|
||||
</Button>
|
||||
)}
|
||||
<PhaseModelSelector
|
||||
compact
|
||||
value={effectiveValue}
|
||||
onChange={handleSetOverride}
|
||||
align="end"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PhaseOverrideItem({
|
||||
phase,
|
||||
project,
|
||||
@@ -234,8 +355,10 @@ export function ProjectModelsSection({ project }: ProjectModelsSectionProps) {
|
||||
useAppStore();
|
||||
const [showBulkReplace, setShowBulkReplace] = useState(false);
|
||||
|
||||
// Count how many overrides are set
|
||||
const overrideCount = Object.keys(project.phaseModelOverrides || {}).length;
|
||||
// Count how many overrides are set (including defaultFeatureModel)
|
||||
const phaseOverrideCount = Object.keys(project.phaseModelOverrides || {}).length;
|
||||
const hasDefaultFeatureModelOverride = !!project.defaultFeatureModel;
|
||||
const overrideCount = phaseOverrideCount + (hasDefaultFeatureModelOverride ? 1 : 0);
|
||||
|
||||
// Check if Claude is available
|
||||
const isClaudeDisabled = disabledProviders.includes('claude');
|
||||
@@ -328,6 +451,9 @@ export function ProjectModelsSection({ project }: ProjectModelsSectionProps) {
|
||||
|
||||
{/* Content */}
|
||||
<div className="p-6 space-y-8">
|
||||
{/* Feature Defaults */}
|
||||
<FeatureDefaultModelOverrideSection project={project} />
|
||||
|
||||
{/* Quick Tasks */}
|
||||
<PhaseGroup
|
||||
title="Quick Tasks"
|
||||
|
||||
Reference in New Issue
Block a user