mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-01 20:23:36 +00:00
feat: implement backlog plan management and UI enhancements
- Added functionality to save, clear, and load backlog plans within the application. - Introduced a new API endpoint for clearing saved backlog plans. - Enhanced the backlog plan dialog to allow users to review and apply changes to their features. - Integrated dependency management features in the UI, allowing users to select parent and child dependencies for features. - Improved the graph view with options to manage plans and visualize dependencies effectively. - Updated the sidebar and settings to include provider visibility toggles for better user control over model selection. These changes aim to enhance the user experience by providing robust backlog management capabilities and improving the overall UI for feature planning.
This commit is contained in:
@@ -6,9 +6,19 @@ import type { Project } from '@/lib/electron';
|
||||
import type { NavigationItem, NavigationGroup } from '../config/navigation';
|
||||
import { GLOBAL_NAV_GROUPS, PROJECT_NAV_ITEMS } from '../config/navigation';
|
||||
import type { SettingsViewId } from '../hooks/use-settings-view';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
import type { ModelProvider } from '@automaker/types';
|
||||
|
||||
const PROVIDERS_DROPDOWN_KEY = 'settings-providers-dropdown-open';
|
||||
|
||||
// Map navigation item IDs to provider types for checking disabled state
|
||||
const NAV_ID_TO_PROVIDER: Record<string, ModelProvider> = {
|
||||
'claude-provider': 'claude',
|
||||
'cursor-provider': 'cursor',
|
||||
'codex-provider': 'codex',
|
||||
'opencode-provider': 'opencode',
|
||||
};
|
||||
|
||||
interface SettingsNavigationProps {
|
||||
navItems: NavigationItem[];
|
||||
activeSection: SettingsViewId;
|
||||
@@ -73,6 +83,8 @@ function NavItemWithSubItems({
|
||||
activeSection: SettingsViewId;
|
||||
onNavigate: (sectionId: SettingsViewId) => void;
|
||||
}) {
|
||||
const disabledProviders = useAppStore((state) => state.disabledProviders);
|
||||
|
||||
const [isOpen, setIsOpen] = useState(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const stored = localStorage.getItem(PROVIDERS_DROPDOWN_KEY);
|
||||
@@ -123,6 +135,9 @@ function NavItemWithSubItems({
|
||||
{item.subItems.map((subItem) => {
|
||||
const SubIcon = subItem.icon;
|
||||
const isSubActive = subItem.id === activeSection;
|
||||
// Check if this provider is disabled
|
||||
const provider = NAV_ID_TO_PROVIDER[subItem.id];
|
||||
const isDisabled = provider && disabledProviders.includes(provider);
|
||||
return (
|
||||
<button
|
||||
key={subItem.id}
|
||||
@@ -141,7 +156,9 @@ function NavItemWithSubItems({
|
||||
'hover:bg-accent/50',
|
||||
'border border-transparent hover:border-border/40',
|
||||
],
|
||||
'hover:scale-[1.01] active:scale-[0.98]'
|
||||
'hover:scale-[1.01] active:scale-[0.98]',
|
||||
// Gray out disabled providers
|
||||
isDisabled && !isSubActive && 'opacity-40'
|
||||
)}
|
||||
>
|
||||
{/* Active indicator bar */}
|
||||
@@ -153,7 +170,9 @@ function NavItemWithSubItems({
|
||||
'w-4 h-4 shrink-0 transition-all duration-200',
|
||||
isSubActive
|
||||
? 'text-brand-500'
|
||||
: 'group-hover:text-brand-400 group-hover:scale-110'
|
||||
: 'group-hover:text-brand-400 group-hover:scale-110',
|
||||
// Gray out icon for disabled providers
|
||||
isDisabled && !isSubActive && 'opacity-60'
|
||||
)}
|
||||
/>
|
||||
<span className="truncate">{subItem.label}</span>
|
||||
|
||||
@@ -168,6 +168,7 @@ export function PhaseModelSelector({
|
||||
dynamicOpencodeModels,
|
||||
opencodeModelsLoading,
|
||||
fetchOpencodeModels,
|
||||
disabledProviders,
|
||||
} = useAppStore();
|
||||
|
||||
// Detect mobile devices to use inline expansion instead of nested popovers
|
||||
@@ -278,8 +279,8 @@ export function PhaseModelSelector({
|
||||
|
||||
// Filter Cursor models to only show enabled ones
|
||||
const availableCursorModels = CURSOR_MODELS.filter((model) => {
|
||||
const cursorId = stripProviderPrefix(model.id) as CursorModelId;
|
||||
return enabledCursorModels.includes(cursorId);
|
||||
// Compare model.id directly since both model.id and enabledCursorModels use full IDs with prefix
|
||||
return enabledCursorModels.includes(model.id as CursorModelId);
|
||||
});
|
||||
|
||||
// Helper to find current selected model details
|
||||
@@ -298,9 +299,7 @@ export function PhaseModelSelector({
|
||||
};
|
||||
}
|
||||
|
||||
const cursorModel = availableCursorModels.find(
|
||||
(m) => stripProviderPrefix(m.id) === selectedModel
|
||||
);
|
||||
const cursorModel = availableCursorModels.find((m) => m.id === selectedModel);
|
||||
if (cursorModel) return { ...cursorModel, icon: CursorIcon };
|
||||
|
||||
// Check if selectedModel is part of a grouped model
|
||||
@@ -400,7 +399,7 @@ export function PhaseModelSelector({
|
||||
return [...staticModels, ...uniqueDynamic];
|
||||
}, [dynamicOpencodeModels]);
|
||||
|
||||
// Group models
|
||||
// Group models (filtering out disabled providers)
|
||||
const { favorites, claude, cursor, codex, opencode } = useMemo(() => {
|
||||
const favs: typeof CLAUDE_MODELS = [];
|
||||
const cModels: typeof CLAUDE_MODELS = [];
|
||||
@@ -408,41 +407,54 @@ export function PhaseModelSelector({
|
||||
const codModels: typeof transformedCodexModels = [];
|
||||
const ocModels: ModelOption[] = [];
|
||||
|
||||
// Process Claude Models
|
||||
CLAUDE_MODELS.forEach((model) => {
|
||||
if (favoriteModels.includes(model.id)) {
|
||||
favs.push(model);
|
||||
} else {
|
||||
cModels.push(model);
|
||||
}
|
||||
});
|
||||
const isClaudeDisabled = disabledProviders.includes('claude');
|
||||
const isCursorDisabled = disabledProviders.includes('cursor');
|
||||
const isCodexDisabled = disabledProviders.includes('codex');
|
||||
const isOpencodeDisabled = disabledProviders.includes('opencode');
|
||||
|
||||
// Process Cursor Models
|
||||
availableCursorModels.forEach((model) => {
|
||||
if (favoriteModels.includes(model.id)) {
|
||||
favs.push(model);
|
||||
} else {
|
||||
curModels.push(model);
|
||||
}
|
||||
});
|
||||
// Process Claude Models (skip if provider is disabled)
|
||||
if (!isClaudeDisabled) {
|
||||
CLAUDE_MODELS.forEach((model) => {
|
||||
if (favoriteModels.includes(model.id)) {
|
||||
favs.push(model);
|
||||
} else {
|
||||
cModels.push(model);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Process Codex Models
|
||||
transformedCodexModels.forEach((model) => {
|
||||
if (favoriteModels.includes(model.id)) {
|
||||
favs.push(model);
|
||||
} else {
|
||||
codModels.push(model);
|
||||
}
|
||||
});
|
||||
// Process Cursor Models (skip if provider is disabled)
|
||||
if (!isCursorDisabled) {
|
||||
availableCursorModels.forEach((model) => {
|
||||
if (favoriteModels.includes(model.id)) {
|
||||
favs.push(model);
|
||||
} else {
|
||||
curModels.push(model);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Process OpenCode Models (including dynamic)
|
||||
allOpencodeModels.forEach((model) => {
|
||||
if (favoriteModels.includes(model.id)) {
|
||||
favs.push(model);
|
||||
} else {
|
||||
ocModels.push(model);
|
||||
}
|
||||
});
|
||||
// Process Codex Models (skip if provider is disabled)
|
||||
if (!isCodexDisabled) {
|
||||
transformedCodexModels.forEach((model) => {
|
||||
if (favoriteModels.includes(model.id)) {
|
||||
favs.push(model);
|
||||
} else {
|
||||
codModels.push(model);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Process OpenCode Models (skip if provider is disabled)
|
||||
if (!isOpencodeDisabled) {
|
||||
allOpencodeModels.forEach((model) => {
|
||||
if (favoriteModels.includes(model.id)) {
|
||||
favs.push(model);
|
||||
} else {
|
||||
ocModels.push(model);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
favorites: favs,
|
||||
@@ -451,7 +463,13 @@ export function PhaseModelSelector({
|
||||
codex: codModels,
|
||||
opencode: ocModels,
|
||||
};
|
||||
}, [favoriteModels, availableCursorModels, transformedCodexModels, allOpencodeModels]);
|
||||
}, [
|
||||
favoriteModels,
|
||||
availableCursorModels,
|
||||
transformedCodexModels,
|
||||
allOpencodeModels,
|
||||
disabledProviders,
|
||||
]);
|
||||
|
||||
// Group OpenCode models by model type for better organization
|
||||
const opencodeSections = useMemo(() => {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { ClaudeMdSettings } from '../claude/claude-md-settings';
|
||||
import { ClaudeUsageSection } from '../api-keys/claude-usage-section';
|
||||
import { SkillsSection } from './claude-settings-tab/skills-section';
|
||||
import { SubagentsSection } from './claude-settings-tab/subagents-section';
|
||||
import { ProviderToggle } from './provider-toggle';
|
||||
import { Info } from 'lucide-react';
|
||||
|
||||
export function ClaudeSettingsTab() {
|
||||
@@ -24,6 +25,9 @@ export function ClaudeSettingsTab() {
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Provider Visibility Toggle */}
|
||||
<ProviderToggle provider="claude" providerLabel="Claude" />
|
||||
|
||||
{/* Usage Info */}
|
||||
<div className="flex items-start gap-3 p-4 rounded-xl bg-blue-500/10 border border-blue-500/20">
|
||||
<Info className="w-5 h-5 text-blue-400 shrink-0 mt-0.5" />
|
||||
|
||||
@@ -5,6 +5,7 @@ import { CodexCliStatus } from '../cli-status/codex-cli-status';
|
||||
import { CodexSettings } from '../codex/codex-settings';
|
||||
import { CodexUsageSection } from '../codex/codex-usage-section';
|
||||
import { CodexModelConfiguration } from './codex-model-configuration';
|
||||
import { ProviderToggle } from './provider-toggle';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import { createLogger } from '@automaker/utils/logger';
|
||||
import type { CliStatus as SharedCliStatus } from '../shared/types';
|
||||
@@ -162,6 +163,9 @@ export function CodexSettingsTab() {
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Provider Visibility Toggle */}
|
||||
<ProviderToggle provider="codex" providerLabel="Codex" />
|
||||
|
||||
<CodexCliStatus
|
||||
status={codexCliStatus}
|
||||
authStatus={authStatusToDisplay}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useCursorStatus } from '../hooks/use-cursor-status';
|
||||
import { useCursorPermissions } from '../hooks/use-cursor-permissions';
|
||||
import { CursorPermissionsSection } from './cursor-permissions-section';
|
||||
import { CursorModelConfiguration } from './cursor-model-configuration';
|
||||
import { ProviderToggle } from './provider-toggle';
|
||||
|
||||
export function CursorSettingsTab() {
|
||||
// Global settings from store
|
||||
@@ -73,6 +74,9 @@ export function CursorSettingsTab() {
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Provider Visibility Toggle */}
|
||||
<ProviderToggle provider="cursor" providerLabel="Cursor" />
|
||||
|
||||
{/* CLI Status */}
|
||||
<CursorCliStatus status={status} isChecking={isLoading} onRefresh={loadData} />
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { toast } from 'sonner';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
import { OpencodeCliStatus, OpencodeCliStatusSkeleton } from '../cli-status/opencode-cli-status';
|
||||
import { OpencodeModelConfiguration } from './opencode-model-configuration';
|
||||
import { ProviderToggle } from './provider-toggle';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import { createLogger } from '@automaker/utils/logger';
|
||||
import type { CliStatus as SharedCliStatus } from '../shared/types';
|
||||
@@ -290,6 +291,9 @@ export function OpencodeSettingsTab() {
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Provider Visibility Toggle */}
|
||||
<ProviderToggle provider="opencode" providerLabel="OpenCode" />
|
||||
|
||||
<OpencodeCliStatus
|
||||
status={cliStatus}
|
||||
authStatus={authStatus}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
import type { ModelProvider } from '@automaker/types';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { EyeOff, Eye } from 'lucide-react';
|
||||
|
||||
interface ProviderToggleProps {
|
||||
provider: ModelProvider;
|
||||
providerLabel: string;
|
||||
}
|
||||
|
||||
export function ProviderToggle({ provider, providerLabel }: ProviderToggleProps) {
|
||||
const { disabledProviders, toggleProviderDisabled } = useAppStore();
|
||||
const isDisabled = disabledProviders.includes(provider);
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between p-4 rounded-xl bg-accent/20 border border-border/30">
|
||||
<div className="flex items-center gap-3">
|
||||
{isDisabled ? (
|
||||
<EyeOff className="w-4 h-4 text-muted-foreground" />
|
||||
) : (
|
||||
<Eye className="w-4 h-4 text-primary" />
|
||||
)}
|
||||
<div>
|
||||
<Label className="text-sm font-medium">Show {providerLabel} in model dropdowns</Label>
|
||||
<p className="text-xs text-muted-foreground mt-0.5">
|
||||
{isDisabled
|
||||
? `${providerLabel} models are hidden from all model selectors`
|
||||
: `${providerLabel} models appear in model selection dropdowns`}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Switch
|
||||
checked={!isDisabled}
|
||||
onCheckedChange={(checked) => toggleProviderDisabled(provider, !checked)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProviderToggle;
|
||||
Reference in New Issue
Block a user