feat: Add ideation context settings

- Add settings popover to the ideation view
- Migrate previous context to toggles (memory, context, features, ideas)
- Add app specifications as new context option
This commit is contained in:
Monoquark
2026-01-24 12:30:20 +01:00
parent 900bbb5e80
commit 5a3dac1533
12 changed files with 573 additions and 91 deletions

View File

@@ -0,0 +1,132 @@
/**
* IdeationSettingsPopover - Configure context sources for idea generation
*/
import { useMemo } from 'react';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { Label } from '@/components/ui/label';
import { Switch } from '@/components/ui/switch';
import { Settings2, FileText, Brain, LayoutGrid, Lightbulb, ScrollText } from 'lucide-react';
import { useShallow } from 'zustand/react/shallow';
import { useIdeationStore } from '@/store/ideation-store';
import { DEFAULT_IDEATION_CONTEXT_SOURCES, type IdeationContextSources } from '@automaker/types';
interface IdeationSettingsPopoverProps {
projectPath: string;
}
const IDEATION_CONTEXT_OPTIONS: Array<{
key: keyof IdeationContextSources;
label: string;
description: string;
icon: typeof FileText;
}> = [
{
key: 'useAppSpec',
label: 'App Specification',
description: 'Overview, capabilities, features',
icon: ScrollText,
},
{
key: 'useContextFiles',
label: 'Context Files',
description: '.automaker/context/*.md|.txt',
icon: FileText,
},
{
key: 'useMemoryFiles',
label: 'Memory Files',
description: '.automaker/memory/*.md',
icon: Brain,
},
{
key: 'useExistingFeatures',
label: 'Existing Features',
description: 'Board features list',
icon: LayoutGrid,
},
{
key: 'useExistingIdeas',
label: 'Existing Ideas',
description: 'Ideation ideas list',
icon: Lightbulb,
},
];
export function IdeationSettingsPopover({ projectPath }: IdeationSettingsPopoverProps) {
const { projectOverrides, setContextSource } = useIdeationStore(
useShallow((state) => ({
projectOverrides: state.contextSourcesByProject[projectPath],
setContextSource: state.setContextSource,
}))
);
const contextSources = useMemo(
() => ({ ...DEFAULT_IDEATION_CONTEXT_SOURCES, ...projectOverrides }),
[projectOverrides]
);
return (
<Popover>
<PopoverTrigger asChild>
<button
type="button"
className="p-1 rounded hover:bg-accent/50 transition-colors"
title="Generation Settings"
aria-label="Generation settings"
data-testid="ideation-context-settings-button"
>
<Settings2 className="w-4 h-4 text-muted-foreground" />
</button>
</PopoverTrigger>
<PopoverContent className="w-80" align="end" sideOffset={8}>
<div className="space-y-3">
<div>
<h4 className="font-medium text-sm mb-1">Generation Settings</h4>
<p className="text-xs text-muted-foreground">
Configure which context sources are included when generating ideas.
</p>
</div>
<div className="space-y-2">
{IDEATION_CONTEXT_OPTIONS.map((option) => {
const Icon = option.icon;
return (
<div
key={option.key}
className="flex items-center justify-between gap-3 p-2 rounded-md bg-secondary/50"
>
<div className="flex items-center gap-2 flex-1 min-w-0">
<Icon className="w-4 h-4 text-brand-500 shrink-0" />
<div className="min-w-0">
<Label
htmlFor={`ideation-context-toggle-${option.key}`}
className="text-xs font-medium cursor-pointer block"
>
{option.label}
</Label>
<span className="text-[10px] text-muted-foreground truncate block">
{option.description}
</span>
</div>
</div>
<Switch
id={`ideation-context-toggle-${option.key}`}
checked={contextSources[option.key]}
onCheckedChange={(checked) =>
setContextSource(projectPath, option.key, checked)
}
data-testid={`ideation-context-toggle-${option.key}`}
/>
</div>
);
})}
</div>
<p className="text-[10px] text-muted-foreground leading-relaxed">
Disable sources to generate more focused ideas or reduce context size.
</p>
</div>
</PopoverContent>
</Popover>
);
}

View File

@@ -13,6 +13,7 @@ import { useGuidedPrompts } from '@/hooks/use-guided-prompts';
import { Button } from '@/components/ui/button';
import { ArrowLeft, ChevronRight, Lightbulb, CheckCheck, Trash2 } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { IdeationSettingsPopover } from './components/ideation-settings-popover';
import type { IdeaCategory } from '@automaker/types';
import type { IdeationMode } from '@/store/ideation-store';
@@ -75,6 +76,7 @@ function IdeationHeader({
discardAllReady,
discardAllCount,
onDiscardAll,
projectPath,
}: {
currentMode: IdeationMode;
selectedCategory: IdeaCategory | null;
@@ -88,6 +90,7 @@ function IdeationHeader({
discardAllReady: boolean;
discardAllCount: number;
onDiscardAll: () => void;
projectPath: string;
}) {
const { getCategoryById } = useGuidedPrompts();
const showBackButton = currentMode === 'prompts';
@@ -157,10 +160,13 @@ function IdeationHeader({
Accept All ({acceptAllCount})
</Button>
)}
<Button onClick={onGenerateIdeas} className="gap-2">
<Lightbulb className="w-4 h-4" />
Generate Ideas
</Button>
<div className="flex items-center gap-1">
<Button onClick={onGenerateIdeas} className="gap-2">
<Lightbulb className="w-4 h-4" />
Generate Ideas
</Button>
<IdeationSettingsPopover projectPath={projectPath} />
</div>
</div>
</div>
);
@@ -282,6 +288,7 @@ export function IdeationView() {
discardAllReady={discardAllReady}
discardAllCount={discardAllCount}
onDiscardAll={handleDiscardAll}
projectPath={currentProject.path}
/>
{/* Dashboard - main view */}

View File

@@ -68,7 +68,16 @@ export function useGenerateIdeationSuggestions(projectPath: string) {
throw new Error('Ideation API not available');
}
const result = await api.ideation.generateSuggestions(projectPath, promptId, category);
// Get context sources from store
const contextSources = useIdeationStore.getState().getContextSources(projectPath);
const result = await api.ideation.generateSuggestions(
projectPath,
promptId,
category,
undefined, // count - use default
contextSources
);
if (!result.success) {
throw new Error(result.error || 'Failed to generate suggestions');

View File

@@ -27,6 +27,7 @@ import type {
CreateIdeaInput,
UpdateIdeaInput,
ConvertToFeatureOptions,
IdeationContextSources,
} from '@automaker/types';
import { DEFAULT_MAX_CONCURRENCY } from '@automaker/types';
import { getJSON, setJSON, removeItem } from './storage';
@@ -114,7 +115,8 @@ export interface IdeationAPI {
projectPath: string,
promptId: string,
category: IdeaCategory,
count?: number
count?: number,
contextSources?: IdeationContextSources
) => Promise<{ success: boolean; suggestions?: AnalysisSuggestion[]; error?: string }>;
// Convert to feature

View File

@@ -32,6 +32,7 @@ import type {
NotificationsAPI,
EventHistoryAPI,
} from './electron';
import type { IdeationContextSources } from '@automaker/types';
import type { EventHistoryFilter } from '@automaker/types';
import type { Message, SessionListItem } from '@/types/electron';
import type { Feature, ClaudeUsageResponse, CodexUsageResponse } from '@/store/app-store';
@@ -2739,9 +2740,16 @@ export class HttpApiClient implements ElectronAPI {
projectPath: string,
promptId: string,
category: IdeaCategory,
count?: number
count?: number,
contextSources?: IdeationContextSources
) =>
this.post('/api/ideation/suggestions/generate', { projectPath, promptId, category, count }),
this.post('/api/ideation/suggestions/generate', {
projectPath,
promptId,
category,
count,
contextSources,
}),
convertToFeature: (projectPath: string, ideaId: string, options?: ConvertToFeatureOptions) =>
this.post('/api/ideation/convert', { projectPath, ideaId, ...options }),

View File

@@ -11,7 +11,9 @@ import type {
IdeationPrompt,
AnalysisSuggestion,
ProjectAnalysisResult,
IdeationContextSources,
} from '@automaker/types';
import { DEFAULT_IDEATION_CONTEXT_SOURCES } from '@automaker/types';
// ============================================================================
// Generation Job Types
@@ -61,6 +63,9 @@ interface IdeationState {
currentMode: IdeationMode;
selectedCategory: IdeaCategory | null;
filterStatus: IdeaStatus | 'all';
// Context sources per project
contextSourcesByProject: Record<string, Partial<IdeationContextSources>>;
}
// ============================================================================
@@ -110,6 +115,14 @@ interface IdeationActions {
setCategory: (category: IdeaCategory | null) => void;
setFilterStatus: (status: IdeaStatus | 'all') => void;
// Context sources
getContextSources: (projectPath: string) => IdeationContextSources;
setContextSource: (
projectPath: string,
key: keyof IdeationContextSources,
value: boolean
) => void;
// Reset
reset: () => void;
resetSuggestions: () => void;
@@ -135,6 +148,7 @@ const initialState: IdeationState = {
currentMode: 'dashboard',
selectedCategory: null,
filterStatus: 'all',
contextSourcesByProject: {},
};
// ============================================================================
@@ -300,6 +314,24 @@ export const useIdeationStore = create<IdeationState & IdeationActions>()(
setFilterStatus: (status) => set({ filterStatus: status }),
// Context sources
getContextSources: (projectPath) => {
const state = get();
const projectOverrides = state.contextSourcesByProject[projectPath] ?? {};
return { ...DEFAULT_IDEATION_CONTEXT_SOURCES, ...projectOverrides };
},
setContextSource: (projectPath, key, value) =>
set((state) => ({
contextSourcesByProject: {
...state.contextSourcesByProject,
[projectPath]: {
...state.contextSourcesByProject[projectPath],
[key]: value,
},
},
})),
// Reset
reset: () => set(initialState),
@@ -313,13 +345,14 @@ export const useIdeationStore = create<IdeationState & IdeationActions>()(
}),
{
name: 'automaker-ideation-store',
version: 4,
version: 5,
partialize: (state) => ({
// Only persist these fields
ideas: state.ideas,
generationJobs: state.generationJobs,
analysisResult: state.analysisResult,
filterStatus: state.filterStatus,
contextSourcesByProject: state.contextSourcesByProject,
}),
migrate: (persistedState: unknown, version: number) => {
const state = persistedState as Record<string, unknown>;
@@ -331,6 +364,13 @@ export const useIdeationStore = create<IdeationState & IdeationActions>()(
generationJobs: jobs.filter((job) => job.projectPath !== undefined),
};
}
if (version < 5) {
// Initialize contextSourcesByProject if not present
return {
...state,
contextSourcesByProject: state.contextSourcesByProject ?? {},
};
}
return state;
},
}