mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-31 20:03:37 +00:00
Merge pull request #650 from AutoMaker-Org/fix/ideation-view-non-claude-models
fix: ideation view not working with other providers
This commit is contained in:
@@ -11,7 +11,6 @@ import { useIdeationStore } from '@/store/ideation-store';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
import { useGenerateIdeationSuggestions } from '@/hooks/mutations';
|
||||
import { toast } from 'sonner';
|
||||
import { useNavigate } from '@tanstack/react-router';
|
||||
import type { IdeaCategory, IdeationPrompt } from '@automaker/types';
|
||||
|
||||
interface PromptListProps {
|
||||
@@ -24,10 +23,8 @@ export function PromptList({ category, onBack }: PromptListProps) {
|
||||
const generationJobs = useIdeationStore((s) => s.generationJobs);
|
||||
const setMode = useIdeationStore((s) => s.setMode);
|
||||
const addGenerationJob = useIdeationStore((s) => s.addGenerationJob);
|
||||
const updateJobStatus = useIdeationStore((s) => s.updateJobStatus);
|
||||
const [loadingPromptId, setLoadingPromptId] = useState<string | null>(null);
|
||||
const [startedPrompts, setStartedPrompts] = useState<Set<string>>(new Set());
|
||||
const navigate = useNavigate();
|
||||
|
||||
// React Query mutation
|
||||
const generateMutation = useGenerateIdeationSuggestions(currentProject?.path ?? '');
|
||||
@@ -72,27 +69,13 @@ export function PromptList({ category, onBack }: PromptListProps) {
|
||||
toast.info(`Generating ideas for "${prompt.title}"...`);
|
||||
setMode('dashboard');
|
||||
|
||||
// Start mutation - onSuccess/onError are handled at the hook level to ensure
|
||||
// they fire even after this component unmounts (which happens due to setMode above)
|
||||
generateMutation.mutate(
|
||||
{ promptId: prompt.id, category },
|
||||
{ promptId: prompt.id, category, jobId, promptTitle: prompt.title },
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
updateJobStatus(jobId, 'ready', data.suggestions);
|
||||
toast.success(`Generated ${data.suggestions.length} ideas for "${prompt.title}"`, {
|
||||
duration: 10000,
|
||||
action: {
|
||||
label: 'View Ideas',
|
||||
onClick: () => {
|
||||
setMode('dashboard');
|
||||
navigate({ to: '/ideation' });
|
||||
},
|
||||
},
|
||||
});
|
||||
setLoadingPromptId(null);
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('Failed to generate suggestions:', error);
|
||||
updateJobStatus(jobId, 'error', undefined, error.message);
|
||||
toast.error(error.message);
|
||||
// Optional: reset local loading state if component is still mounted
|
||||
onSettled: () => {
|
||||
setLoadingPromptId(null);
|
||||
},
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ const PHASE_LABELS: Record<PhaseModelKey, string> = {
|
||||
featureGenerationModel: 'Feature Generation',
|
||||
backlogPlanningModel: 'Backlog Planning',
|
||||
projectAnalysisModel: 'Project Analysis',
|
||||
suggestionsModel: 'AI Suggestions',
|
||||
ideationModel: 'Ideation',
|
||||
memoryExtractionModel: 'Memory Extraction',
|
||||
};
|
||||
|
||||
|
||||
@@ -72,9 +72,9 @@ const GENERATION_TASKS: PhaseConfig[] = [
|
||||
description: 'Analyzes project structure for suggestions',
|
||||
},
|
||||
{
|
||||
key: 'suggestionsModel',
|
||||
label: 'AI Suggestions',
|
||||
description: 'Model for feature, refactoring, security, and performance suggestions',
|
||||
key: 'ideationModel',
|
||||
label: 'Ideation',
|
||||
description: 'Model for ideation view (generating AI suggestions)',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ const PHASE_LABELS: Record<PhaseModelKey, string> = {
|
||||
featureGenerationModel: 'Feature Generation',
|
||||
backlogPlanningModel: 'Backlog Planning',
|
||||
projectAnalysisModel: 'Project Analysis',
|
||||
suggestionsModel: 'AI Suggestions',
|
||||
ideationModel: 'Ideation',
|
||||
memoryExtractionModel: 'Memory Extraction',
|
||||
};
|
||||
|
||||
|
||||
@@ -67,9 +67,9 @@ const GENERATION_TASKS: PhaseConfig[] = [
|
||||
description: 'Analyzes project structure for suggestions',
|
||||
},
|
||||
{
|
||||
key: 'suggestionsModel',
|
||||
label: 'AI Suggestions',
|
||||
description: 'Model for feature, refactoring, security, and performance suggestions',
|
||||
key: 'ideationModel',
|
||||
label: 'Ideation',
|
||||
description: 'Model for ideation view (generating AI suggestions)',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import { queryKeys } from '@/lib/query-keys';
|
||||
import { toast } from 'sonner';
|
||||
import type { IdeaCategory, IdeaSuggestion } from '@automaker/types';
|
||||
import type { IdeaCategory, AnalysisSuggestion } from '@automaker/types';
|
||||
import { useIdeationStore } from '@/store/ideation-store';
|
||||
|
||||
/**
|
||||
* Input for generating ideation suggestions
|
||||
@@ -16,15 +17,23 @@ import type { IdeaCategory, IdeaSuggestion } from '@automaker/types';
|
||||
interface GenerateSuggestionsInput {
|
||||
promptId: string;
|
||||
category: IdeaCategory;
|
||||
/** Job ID for tracking generation progress - used to update job status on completion */
|
||||
jobId: string;
|
||||
/** Prompt title for toast notifications */
|
||||
promptTitle: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Result from generating suggestions
|
||||
*/
|
||||
interface GenerateSuggestionsResult {
|
||||
suggestions: IdeaSuggestion[];
|
||||
suggestions: AnalysisSuggestion[];
|
||||
promptId: string;
|
||||
category: IdeaCategory;
|
||||
/** Job ID passed through for onSuccess handler */
|
||||
jobId: string;
|
||||
/** Prompt title passed through for toast notifications */
|
||||
promptTitle: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,7 +61,7 @@ export function useGenerateIdeationSuggestions(projectPath: string) {
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (input: GenerateSuggestionsInput): Promise<GenerateSuggestionsResult> => {
|
||||
const { promptId, category } = input;
|
||||
const { promptId, category, jobId, promptTitle } = input;
|
||||
|
||||
const api = getElectronAPI();
|
||||
if (!api.ideation?.generateSuggestions) {
|
||||
@@ -69,14 +78,33 @@ export function useGenerateIdeationSuggestions(projectPath: string) {
|
||||
suggestions: result.suggestions ?? [],
|
||||
promptId,
|
||||
category,
|
||||
jobId,
|
||||
promptTitle,
|
||||
};
|
||||
},
|
||||
onSuccess: () => {
|
||||
onSuccess: (data) => {
|
||||
// Update job status in Zustand store - this runs even if the component unmounts
|
||||
// Using getState() to access store directly without hooks (safe in callbacks)
|
||||
const updateJobStatus = useIdeationStore.getState().updateJobStatus;
|
||||
updateJobStatus(data.jobId, 'ready', data.suggestions);
|
||||
|
||||
// Show success toast
|
||||
toast.success(`Generated ${data.suggestions.length} ideas for "${data.promptTitle}"`, {
|
||||
duration: 10000,
|
||||
});
|
||||
|
||||
// Invalidate ideation ideas cache
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.ideation.ideas(projectPath),
|
||||
});
|
||||
},
|
||||
// Toast notifications are handled by the component since it has access to prompt title
|
||||
onError: (error, variables) => {
|
||||
// Update job status to error - this runs even if the component unmounts
|
||||
const updateJobStatus = useIdeationStore.getState().updateJobStatus;
|
||||
updateJobStatus(variables.jobId, 'error', undefined, error.message);
|
||||
|
||||
// Show error toast
|
||||
toast.error(`Failed to generate ideas: ${error.message}`);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -596,7 +596,7 @@ export async function refreshSettingsFromServer(): Promise<boolean> {
|
||||
projectAnalysisModel: migratePhaseModelEntry(
|
||||
serverSettings.phaseModels.projectAnalysisModel
|
||||
),
|
||||
suggestionsModel: migratePhaseModelEntry(serverSettings.phaseModels.suggestionsModel),
|
||||
ideationModel: migratePhaseModelEntry(serverSettings.phaseModels.ideationModel),
|
||||
memoryExtractionModel: migratePhaseModelEntry(
|
||||
serverSettings.phaseModels.memoryExtractionModel
|
||||
),
|
||||
|
||||
@@ -370,40 +370,6 @@ export interface GitHubAPI {
|
||||
}>;
|
||||
}
|
||||
|
||||
// Feature Suggestions types
|
||||
export interface FeatureSuggestion {
|
||||
id: string;
|
||||
category: string;
|
||||
description: string;
|
||||
priority: number;
|
||||
reasoning: string;
|
||||
}
|
||||
|
||||
export interface SuggestionsEvent {
|
||||
type: 'suggestions_progress' | 'suggestions_tool' | 'suggestions_complete' | 'suggestions_error';
|
||||
content?: string;
|
||||
tool?: string;
|
||||
input?: unknown;
|
||||
suggestions?: FeatureSuggestion[];
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export type SuggestionType = 'features' | 'refactoring' | 'security' | 'performance';
|
||||
|
||||
export interface SuggestionsAPI {
|
||||
generate: (
|
||||
projectPath: string,
|
||||
suggestionType?: SuggestionType
|
||||
) => Promise<{ success: boolean; error?: string }>;
|
||||
stop: () => Promise<{ success: boolean; error?: string }>;
|
||||
status: () => Promise<{
|
||||
success: boolean;
|
||||
isRunning?: boolean;
|
||||
error?: string;
|
||||
}>;
|
||||
onEvent: (callback: (event: SuggestionsEvent) => void) => () => void;
|
||||
}
|
||||
|
||||
// Spec Regeneration types
|
||||
export type SpecRegenerationEvent =
|
||||
| { type: 'spec_regeneration_progress'; content: string; projectPath: string }
|
||||
@@ -702,7 +668,6 @@ export interface ElectronAPI {
|
||||
};
|
||||
worktree?: WorktreeAPI;
|
||||
git?: GitAPI;
|
||||
suggestions?: SuggestionsAPI;
|
||||
specRegeneration?: SpecRegenerationAPI;
|
||||
autoMode?: AutoModeAPI;
|
||||
features?: FeaturesAPI;
|
||||
@@ -1333,9 +1298,6 @@ const getMockElectronAPI = (): ElectronAPI => {
|
||||
// Mock Git API (for non-worktree operations)
|
||||
git: createMockGitAPI(),
|
||||
|
||||
// Mock Suggestions API
|
||||
suggestions: createMockSuggestionsAPI(),
|
||||
|
||||
// Mock Spec Regeneration API
|
||||
specRegeneration: createMockSpecRegenerationAPI(),
|
||||
|
||||
@@ -2605,226 +2567,6 @@ function delay(ms: number, featureId: string): Promise<void> {
|
||||
});
|
||||
}
|
||||
|
||||
// Mock Suggestions state and implementation
|
||||
let mockSuggestionsRunning = false;
|
||||
let mockSuggestionsCallbacks: ((event: SuggestionsEvent) => void)[] = [];
|
||||
let mockSuggestionsTimeout: NodeJS.Timeout | null = null;
|
||||
|
||||
function createMockSuggestionsAPI(): SuggestionsAPI {
|
||||
return {
|
||||
generate: async (projectPath: string, suggestionType: SuggestionType = 'features') => {
|
||||
if (mockSuggestionsRunning) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Suggestions generation is already running',
|
||||
};
|
||||
}
|
||||
|
||||
mockSuggestionsRunning = true;
|
||||
console.log(`[Mock] Generating ${suggestionType} suggestions for: ${projectPath}`);
|
||||
|
||||
// Simulate async suggestion generation
|
||||
simulateSuggestionsGeneration(suggestionType);
|
||||
|
||||
return { success: true };
|
||||
},
|
||||
|
||||
stop: async () => {
|
||||
mockSuggestionsRunning = false;
|
||||
if (mockSuggestionsTimeout) {
|
||||
clearTimeout(mockSuggestionsTimeout);
|
||||
mockSuggestionsTimeout = null;
|
||||
}
|
||||
return { success: true };
|
||||
},
|
||||
|
||||
status: async () => {
|
||||
return {
|
||||
success: true,
|
||||
isRunning: mockSuggestionsRunning,
|
||||
};
|
||||
},
|
||||
|
||||
onEvent: (callback: (event: SuggestionsEvent) => void) => {
|
||||
mockSuggestionsCallbacks.push(callback);
|
||||
return () => {
|
||||
mockSuggestionsCallbacks = mockSuggestionsCallbacks.filter((cb) => cb !== callback);
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function emitSuggestionsEvent(event: SuggestionsEvent) {
|
||||
mockSuggestionsCallbacks.forEach((cb) => cb(event));
|
||||
}
|
||||
|
||||
async function simulateSuggestionsGeneration(suggestionType: SuggestionType = 'features') {
|
||||
const typeLabels: Record<SuggestionType, string> = {
|
||||
features: 'feature suggestions',
|
||||
refactoring: 'refactoring opportunities',
|
||||
security: 'security vulnerabilities',
|
||||
performance: 'performance issues',
|
||||
};
|
||||
|
||||
// Emit progress events
|
||||
emitSuggestionsEvent({
|
||||
type: 'suggestions_progress',
|
||||
content: `Starting project analysis for ${typeLabels[suggestionType]}...\n`,
|
||||
});
|
||||
|
||||
await new Promise((resolve) => {
|
||||
mockSuggestionsTimeout = setTimeout(resolve, 500);
|
||||
});
|
||||
if (!mockSuggestionsRunning) return;
|
||||
|
||||
emitSuggestionsEvent({
|
||||
type: 'suggestions_tool',
|
||||
tool: 'Glob',
|
||||
input: { pattern: '**/*.{ts,tsx,js,jsx}' },
|
||||
});
|
||||
|
||||
await new Promise((resolve) => {
|
||||
mockSuggestionsTimeout = setTimeout(resolve, 500);
|
||||
});
|
||||
if (!mockSuggestionsRunning) return;
|
||||
|
||||
emitSuggestionsEvent({
|
||||
type: 'suggestions_progress',
|
||||
content: 'Analyzing codebase structure...\n',
|
||||
});
|
||||
|
||||
await new Promise((resolve) => {
|
||||
mockSuggestionsTimeout = setTimeout(resolve, 500);
|
||||
});
|
||||
if (!mockSuggestionsRunning) return;
|
||||
|
||||
emitSuggestionsEvent({
|
||||
type: 'suggestions_progress',
|
||||
content: `Identifying ${typeLabels[suggestionType]}...\n`,
|
||||
});
|
||||
|
||||
await new Promise((resolve) => {
|
||||
mockSuggestionsTimeout = setTimeout(resolve, 500);
|
||||
});
|
||||
if (!mockSuggestionsRunning) return;
|
||||
|
||||
// Generate mock suggestions based on type
|
||||
let mockSuggestions: FeatureSuggestion[];
|
||||
|
||||
switch (suggestionType) {
|
||||
case 'refactoring':
|
||||
mockSuggestions = [
|
||||
{
|
||||
id: `suggestion-${Date.now()}-0`,
|
||||
category: 'Code Smell',
|
||||
description: 'Extract duplicate validation logic into reusable utility',
|
||||
priority: 1,
|
||||
reasoning: 'Reduces code duplication and improves maintainability',
|
||||
},
|
||||
{
|
||||
id: `suggestion-${Date.now()}-1`,
|
||||
category: 'Complexity',
|
||||
description: 'Break down large handleSubmit function into smaller functions',
|
||||
priority: 2,
|
||||
reasoning: 'Function is too long and handles multiple responsibilities',
|
||||
},
|
||||
{
|
||||
id: `suggestion-${Date.now()}-2`,
|
||||
category: 'Architecture',
|
||||
description: 'Move business logic out of React components into hooks',
|
||||
priority: 3,
|
||||
reasoning: 'Improves separation of concerns and testability',
|
||||
},
|
||||
];
|
||||
break;
|
||||
|
||||
case 'security':
|
||||
mockSuggestions = [
|
||||
{
|
||||
id: `suggestion-${Date.now()}-0`,
|
||||
category: 'High',
|
||||
description: 'Sanitize user input before rendering to prevent XSS',
|
||||
priority: 1,
|
||||
reasoning: 'User input is rendered without proper sanitization',
|
||||
},
|
||||
{
|
||||
id: `suggestion-${Date.now()}-1`,
|
||||
category: 'Medium',
|
||||
description: 'Add rate limiting to authentication endpoints',
|
||||
priority: 2,
|
||||
reasoning: 'Prevents brute force attacks on authentication',
|
||||
},
|
||||
{
|
||||
id: `suggestion-${Date.now()}-2`,
|
||||
category: 'Low',
|
||||
description: 'Remove sensitive information from error messages',
|
||||
priority: 3,
|
||||
reasoning: 'Error messages may leak implementation details',
|
||||
},
|
||||
];
|
||||
break;
|
||||
|
||||
case 'performance':
|
||||
mockSuggestions = [
|
||||
{
|
||||
id: `suggestion-${Date.now()}-0`,
|
||||
category: 'Rendering',
|
||||
description: 'Add React.memo to prevent unnecessary re-renders',
|
||||
priority: 1,
|
||||
reasoning: "Components re-render even when props haven't changed",
|
||||
},
|
||||
{
|
||||
id: `suggestion-${Date.now()}-1`,
|
||||
category: 'Bundle Size',
|
||||
description: 'Implement code splitting for route components',
|
||||
priority: 2,
|
||||
reasoning: 'Initial bundle is larger than necessary',
|
||||
},
|
||||
{
|
||||
id: `suggestion-${Date.now()}-2`,
|
||||
category: 'Caching',
|
||||
description: 'Add memoization for expensive computations',
|
||||
priority: 3,
|
||||
reasoning: 'Expensive computations run on every render',
|
||||
},
|
||||
];
|
||||
break;
|
||||
|
||||
default: // "features"
|
||||
mockSuggestions = [
|
||||
{
|
||||
id: `suggestion-${Date.now()}-0`,
|
||||
category: 'User Experience',
|
||||
description: 'Add dark mode toggle with system preference detection',
|
||||
priority: 1,
|
||||
reasoning: 'Dark mode is a standard feature that improves accessibility and user comfort',
|
||||
},
|
||||
{
|
||||
id: `suggestion-${Date.now()}-1`,
|
||||
category: 'Performance',
|
||||
description: 'Implement lazy loading for heavy components',
|
||||
priority: 2,
|
||||
reasoning: 'Improves initial load time and reduces bundle size',
|
||||
},
|
||||
{
|
||||
id: `suggestion-${Date.now()}-2`,
|
||||
category: 'Accessibility',
|
||||
description: 'Add keyboard navigation support throughout the app',
|
||||
priority: 3,
|
||||
reasoning: 'Improves accessibility for users who rely on keyboard navigation',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
emitSuggestionsEvent({
|
||||
type: 'suggestions_complete',
|
||||
suggestions: mockSuggestions,
|
||||
});
|
||||
|
||||
mockSuggestionsRunning = false;
|
||||
mockSuggestionsTimeout = null;
|
||||
}
|
||||
|
||||
// Mock Spec Regeneration state and implementation
|
||||
let mockSpecRegenerationRunning = false;
|
||||
let mockSpecRegenerationPhase = '';
|
||||
|
||||
@@ -16,12 +16,9 @@ import type {
|
||||
SaveImageResult,
|
||||
AutoModeAPI,
|
||||
FeaturesAPI,
|
||||
SuggestionsAPI,
|
||||
SpecRegenerationAPI,
|
||||
AutoModeEvent,
|
||||
SuggestionsEvent,
|
||||
SpecRegenerationEvent,
|
||||
SuggestionType,
|
||||
GitHubAPI,
|
||||
IssueValidationInput,
|
||||
IssueValidationEvent,
|
||||
@@ -550,7 +547,6 @@ export const checkSandboxEnvironment = async (): Promise<{
|
||||
type EventType =
|
||||
| 'agent:stream'
|
||||
| 'auto-mode:event'
|
||||
| 'suggestions:event'
|
||||
| 'spec-regeneration:event'
|
||||
| 'issue-validation:event'
|
||||
| 'backlog-plan:event'
|
||||
@@ -1983,22 +1979,6 @@ export class HttpApiClient implements ElectronAPI {
|
||||
this.post('/api/git/file-diff', { projectPath, filePath }),
|
||||
};
|
||||
|
||||
// Suggestions API
|
||||
suggestions: SuggestionsAPI = {
|
||||
generate: (
|
||||
projectPath: string,
|
||||
suggestionType?: SuggestionType,
|
||||
model?: string,
|
||||
thinkingLevel?: string
|
||||
) =>
|
||||
this.post('/api/suggestions/generate', { projectPath, suggestionType, model, thinkingLevel }),
|
||||
stop: () => this.post('/api/suggestions/stop'),
|
||||
status: () => this.get('/api/suggestions/status'),
|
||||
onEvent: (callback: (event: SuggestionsEvent) => void) => {
|
||||
return this.subscribeToEvent('suggestions:event', callback as EventCallback);
|
||||
},
|
||||
};
|
||||
|
||||
// Spec Regeneration API
|
||||
specRegeneration: SpecRegenerationAPI = {
|
||||
create: (
|
||||
|
||||
Reference in New Issue
Block a user