mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-02 20:43:36 +00:00
refactor(ui): migrate remaining components to React Query
- Migrate workspace-picker-modal to useWorkspaceDirectories query - Migrate session-manager to useSessions query - Migrate git-diff-panel to useGitDiffs query - Migrate prompt-list to useIdeationPrompts query - Migrate spec-view hooks to useSpecFile query and spec mutations - Migrate use-board-background-settings to useProjectSettings query - Migrate use-guided-prompts to useIdeationPrompts query - Migrate use-project-settings-loader to React Query - Complete React Query migration across all components Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,7 @@ import { Card, CardContent } from '@/components/ui/card';
|
||||
import { useGuidedPrompts } from '@/hooks/use-guided-prompts';
|
||||
import { useIdeationStore } from '@/store/ideation-store';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import { useGenerateIdeationSuggestions } from '@/hooks/mutations';
|
||||
import { toast } from 'sonner';
|
||||
import { useNavigate } from '@tanstack/react-router';
|
||||
import type { IdeaCategory, IdeationPrompt } from '@automaker/types';
|
||||
@@ -27,6 +27,9 @@ export function PromptList({ category, onBack }: PromptListProps) {
|
||||
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 ?? '');
|
||||
const {
|
||||
getPromptsByCategory,
|
||||
isLoading: isLoadingPrompts,
|
||||
@@ -56,7 +59,7 @@ export function PromptList({ category, onBack }: PromptListProps) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (loadingPromptId || generatingPromptIds.has(prompt.id)) return;
|
||||
if (loadingPromptId || generateMutation.isPending || generatingPromptIds.has(prompt.id)) return;
|
||||
|
||||
setLoadingPromptId(prompt.id);
|
||||
|
||||
@@ -68,42 +71,31 @@ export function PromptList({ category, onBack }: PromptListProps) {
|
||||
toast.info(`Generating ideas for "${prompt.title}"...`);
|
||||
setMode('dashboard');
|
||||
|
||||
try {
|
||||
const api = getElectronAPI();
|
||||
const result = await api.ideation?.generateSuggestions(
|
||||
currentProject.path,
|
||||
prompt.id,
|
||||
category
|
||||
);
|
||||
|
||||
if (result?.success && result.suggestions) {
|
||||
updateJobStatus(jobId, 'ready', result.suggestions);
|
||||
toast.success(`Generated ${result.suggestions.length} ideas for "${prompt.title}"`, {
|
||||
duration: 10000,
|
||||
action: {
|
||||
label: 'View Ideas',
|
||||
onClick: () => {
|
||||
setMode('dashboard');
|
||||
navigate({ to: '/ideation' });
|
||||
generateMutation.mutate(
|
||||
{ promptId: prompt.id, category },
|
||||
{
|
||||
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' });
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
} else {
|
||||
updateJobStatus(
|
||||
jobId,
|
||||
'error',
|
||||
undefined,
|
||||
result?.error || 'Failed to generate suggestions'
|
||||
);
|
||||
toast.error(result?.error || 'Failed to generate suggestions');
|
||||
});
|
||||
setLoadingPromptId(null);
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('Failed to generate suggestions:', error);
|
||||
updateJobStatus(jobId, 'error', undefined, error.message);
|
||||
toast.error(error.message);
|
||||
setLoadingPromptId(null);
|
||||
},
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to generate suggestions:', error);
|
||||
updateJobStatus(jobId, 'error', undefined, (error as Error).message);
|
||||
toast.error((error as Error).message);
|
||||
} finally {
|
||||
setLoadingPromptId(null);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -10,6 +10,7 @@ import { createElement } from 'react';
|
||||
import { SPEC_FILE_WRITE_DELAY, STATUS_CHECK_INTERVAL_MS } from '../constants';
|
||||
import type { FeatureCount } from '../types';
|
||||
import type { SpecRegenerationEvent } from '@/types/electron';
|
||||
import { useCreateSpec, useRegenerateSpec, useGenerateFeatures } from '@/hooks/mutations';
|
||||
|
||||
interface UseSpecGenerationOptions {
|
||||
loadSpec: () => Promise<void>;
|
||||
@@ -18,6 +19,11 @@ interface UseSpecGenerationOptions {
|
||||
export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) {
|
||||
const { currentProject } = useAppStore();
|
||||
|
||||
// React Query mutations
|
||||
const createSpecMutation = useCreateSpec(currentProject?.path ?? '');
|
||||
const regenerateSpecMutation = useRegenerateSpec(currentProject?.path ?? '');
|
||||
const generateFeaturesMutation = useGenerateFeatures(currentProject?.path ?? '');
|
||||
|
||||
// Dialog visibility state
|
||||
const [showCreateDialog, setShowCreateDialog] = useState(false);
|
||||
const [showRegenerateDialog, setShowRegenerateDialog] = useState(false);
|
||||
@@ -404,47 +410,34 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) {
|
||||
logsRef.current = '';
|
||||
setLogs('');
|
||||
logger.debug('[useSpecGeneration] Starting spec creation, generateFeatures:', generateFeatures);
|
||||
try {
|
||||
const api = getElectronAPI();
|
||||
if (!api.specRegeneration) {
|
||||
logger.error('[useSpecGeneration] Spec regeneration not available');
|
||||
setIsCreating(false);
|
||||
return;
|
||||
}
|
||||
const result = await api.specRegeneration.create(
|
||||
currentProject.path,
|
||||
projectOverview.trim(),
|
||||
generateFeatures,
|
||||
analyzeProjectOnCreate,
|
||||
generateFeatures ? featureCountOnCreate : undefined
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
const errorMsg = result.error || 'Unknown error';
|
||||
logger.error('[useSpecGeneration] Failed to start spec creation:', errorMsg);
|
||||
setIsCreating(false);
|
||||
setCurrentPhase('error');
|
||||
setErrorMessage(errorMsg);
|
||||
const errorLog = `[Error] Failed to start spec creation: ${errorMsg}\n`;
|
||||
logsRef.current = errorLog;
|
||||
setLogs(errorLog);
|
||||
createSpecMutation.mutate(
|
||||
{
|
||||
projectOverview: projectOverview.trim(),
|
||||
generateFeatures,
|
||||
analyzeProject: analyzeProjectOnCreate,
|
||||
featureCount: generateFeatures ? featureCountOnCreate : undefined,
|
||||
},
|
||||
{
|
||||
onError: (error) => {
|
||||
const errorMsg = error.message;
|
||||
logger.error('[useSpecGeneration] Failed to create spec:', errorMsg);
|
||||
setIsCreating(false);
|
||||
setCurrentPhase('error');
|
||||
setErrorMessage(errorMsg);
|
||||
const errorLog = `[Error] Failed to create spec: ${errorMsg}\n`;
|
||||
logsRef.current = errorLog;
|
||||
setLogs(errorLog);
|
||||
},
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||
logger.error('[useSpecGeneration] Failed to create spec:', errorMsg);
|
||||
setIsCreating(false);
|
||||
setCurrentPhase('error');
|
||||
setErrorMessage(errorMsg);
|
||||
const errorLog = `[Error] Failed to create spec: ${errorMsg}\n`;
|
||||
logsRef.current = errorLog;
|
||||
setLogs(errorLog);
|
||||
}
|
||||
);
|
||||
}, [
|
||||
currentProject,
|
||||
projectOverview,
|
||||
generateFeatures,
|
||||
analyzeProjectOnCreate,
|
||||
featureCountOnCreate,
|
||||
createSpecMutation,
|
||||
]);
|
||||
|
||||
const handleRegenerate = useCallback(async () => {
|
||||
@@ -460,47 +453,34 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) {
|
||||
'[useSpecGeneration] Starting spec regeneration, generateFeatures:',
|
||||
generateFeaturesOnRegenerate
|
||||
);
|
||||
try {
|
||||
const api = getElectronAPI();
|
||||
if (!api.specRegeneration) {
|
||||
logger.error('[useSpecGeneration] Spec regeneration not available');
|
||||
setIsRegenerating(false);
|
||||
return;
|
||||
}
|
||||
const result = await api.specRegeneration.generate(
|
||||
currentProject.path,
|
||||
projectDefinition.trim(),
|
||||
generateFeaturesOnRegenerate,
|
||||
analyzeProjectOnRegenerate,
|
||||
generateFeaturesOnRegenerate ? featureCountOnRegenerate : undefined
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
const errorMsg = result.error || 'Unknown error';
|
||||
logger.error('[useSpecGeneration] Failed to start regeneration:', errorMsg);
|
||||
setIsRegenerating(false);
|
||||
setCurrentPhase('error');
|
||||
setErrorMessage(errorMsg);
|
||||
const errorLog = `[Error] Failed to start regeneration: ${errorMsg}\n`;
|
||||
logsRef.current = errorLog;
|
||||
setLogs(errorLog);
|
||||
regenerateSpecMutation.mutate(
|
||||
{
|
||||
projectDefinition: projectDefinition.trim(),
|
||||
generateFeatures: generateFeaturesOnRegenerate,
|
||||
analyzeProject: analyzeProjectOnRegenerate,
|
||||
featureCount: generateFeaturesOnRegenerate ? featureCountOnRegenerate : undefined,
|
||||
},
|
||||
{
|
||||
onError: (error) => {
|
||||
const errorMsg = error.message;
|
||||
logger.error('[useSpecGeneration] Failed to regenerate spec:', errorMsg);
|
||||
setIsRegenerating(false);
|
||||
setCurrentPhase('error');
|
||||
setErrorMessage(errorMsg);
|
||||
const errorLog = `[Error] Failed to regenerate spec: ${errorMsg}\n`;
|
||||
logsRef.current = errorLog;
|
||||
setLogs(errorLog);
|
||||
},
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||
logger.error('[useSpecGeneration] Failed to regenerate spec:', errorMsg);
|
||||
setIsRegenerating(false);
|
||||
setCurrentPhase('error');
|
||||
setErrorMessage(errorMsg);
|
||||
const errorLog = `[Error] Failed to regenerate spec: ${errorMsg}\n`;
|
||||
logsRef.current = errorLog;
|
||||
setLogs(errorLog);
|
||||
}
|
||||
);
|
||||
}, [
|
||||
currentProject,
|
||||
projectDefinition,
|
||||
generateFeaturesOnRegenerate,
|
||||
analyzeProjectOnRegenerate,
|
||||
featureCountOnRegenerate,
|
||||
regenerateSpecMutation,
|
||||
]);
|
||||
|
||||
const handleGenerateFeatures = useCallback(async () => {
|
||||
@@ -513,36 +493,20 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) {
|
||||
logsRef.current = '';
|
||||
setLogs('');
|
||||
logger.debug('[useSpecGeneration] Starting feature generation from existing spec');
|
||||
try {
|
||||
const api = getElectronAPI();
|
||||
if (!api.specRegeneration) {
|
||||
logger.error('[useSpecGeneration] Spec regeneration not available');
|
||||
setIsGeneratingFeatures(false);
|
||||
return;
|
||||
}
|
||||
const result = await api.specRegeneration.generateFeatures(currentProject.path);
|
||||
|
||||
if (!result.success) {
|
||||
const errorMsg = result.error || 'Unknown error';
|
||||
logger.error('[useSpecGeneration] Failed to start feature generation:', errorMsg);
|
||||
generateFeaturesMutation.mutate(undefined, {
|
||||
onError: (error) => {
|
||||
const errorMsg = error.message;
|
||||
logger.error('[useSpecGeneration] Failed to generate features:', errorMsg);
|
||||
setIsGeneratingFeatures(false);
|
||||
setCurrentPhase('error');
|
||||
setErrorMessage(errorMsg);
|
||||
const errorLog = `[Error] Failed to start feature generation: ${errorMsg}\n`;
|
||||
const errorLog = `[Error] Failed to generate features: ${errorMsg}\n`;
|
||||
logsRef.current = errorLog;
|
||||
setLogs(errorLog);
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||
logger.error('[useSpecGeneration] Failed to generate features:', errorMsg);
|
||||
setIsGeneratingFeatures(false);
|
||||
setCurrentPhase('error');
|
||||
setErrorMessage(errorMsg);
|
||||
const errorLog = `[Error] Failed to generate features: ${errorMsg}\n`;
|
||||
logsRef.current = errorLog;
|
||||
setLogs(errorLog);
|
||||
}
|
||||
}, [currentProject]);
|
||||
},
|
||||
});
|
||||
}, [currentProject, generateFeaturesMutation]);
|
||||
|
||||
return {
|
||||
// Dialog state
|
||||
|
||||
@@ -1,61 +1,53 @@
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import { createLogger } from '@automaker/utils/logger';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
|
||||
const logger = createLogger('SpecLoading');
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import { useSpecFile, useSpecRegenerationStatus } from '@/hooks/queries';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { queryKeys } from '@/lib/query-keys';
|
||||
|
||||
export function useSpecLoading() {
|
||||
const { currentProject, setAppSpec } = useAppStore();
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const queryClient = useQueryClient();
|
||||
const [specExists, setSpecExists] = useState(true);
|
||||
const [isGenerationRunning, setIsGenerationRunning] = useState(false);
|
||||
|
||||
const loadSpec = useCallback(async () => {
|
||||
if (!currentProject) return;
|
||||
// React Query hooks
|
||||
const specFileQuery = useSpecFile(currentProject?.path);
|
||||
const statusQuery = useSpecRegenerationStatus(currentProject?.path);
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const api = getElectronAPI();
|
||||
|
||||
// Check if spec generation is running before trying to load
|
||||
// This prevents showing "No App Specification Found" during generation
|
||||
if (api.specRegeneration) {
|
||||
const status = await api.specRegeneration.status(currentProject.path);
|
||||
if (status.success && status.isRunning) {
|
||||
logger.debug('Spec generation is running for this project, skipping load');
|
||||
setIsGenerationRunning(true);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Always reset when generation is not running (handles edge case where api.specRegeneration might not be available)
|
||||
setIsGenerationRunning(false);
|
||||
|
||||
const result = await api.readFile(`${currentProject.path}/.automaker/app_spec.txt`);
|
||||
|
||||
if (result.success && result.content) {
|
||||
setAppSpec(result.content);
|
||||
setSpecExists(true);
|
||||
} else {
|
||||
// File doesn't exist
|
||||
setAppSpec('');
|
||||
setSpecExists(false);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to load spec:', error);
|
||||
setSpecExists(false);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [currentProject, setAppSpec]);
|
||||
const isGenerationRunning = statusQuery.data?.isRunning ?? false;
|
||||
|
||||
// Update app store and specExists when spec file data changes
|
||||
useEffect(() => {
|
||||
loadSpec();
|
||||
}, [loadSpec]);
|
||||
if (specFileQuery.data && !isGenerationRunning) {
|
||||
setAppSpec(specFileQuery.data.content);
|
||||
setSpecExists(specFileQuery.data.exists);
|
||||
}
|
||||
}, [specFileQuery.data, setAppSpec, isGenerationRunning]);
|
||||
|
||||
// Manual reload function (invalidates cache)
|
||||
const loadSpec = useCallback(async () => {
|
||||
if (!currentProject?.path) return;
|
||||
|
||||
// First check if generation is running
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.specRegeneration.status(currentProject.path),
|
||||
});
|
||||
|
||||
const statusData = queryClient.getQueryData<{ isRunning: boolean }>(
|
||||
queryKeys.specRegeneration.status(currentProject.path)
|
||||
);
|
||||
|
||||
if (statusData?.isRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Invalidate and refetch spec file
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.spec.file(currentProject.path),
|
||||
});
|
||||
}, [currentProject?.path, queryClient]);
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
isLoading: specFileQuery.isLoading,
|
||||
specExists,
|
||||
setSpecExists,
|
||||
isGenerationRunning,
|
||||
|
||||
@@ -1,28 +1,20 @@
|
||||
import { useState } from 'react';
|
||||
import { createLogger } from '@automaker/utils/logger';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
|
||||
const logger = createLogger('SpecSave');
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import { useSaveSpec } from '@/hooks/mutations';
|
||||
|
||||
export function useSpecSave() {
|
||||
const { currentProject, appSpec, setAppSpec } = useAppStore();
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [hasChanges, setHasChanges] = useState(false);
|
||||
|
||||
// React Query mutation
|
||||
const saveMutation = useSaveSpec(currentProject?.path ?? '');
|
||||
|
||||
const saveSpec = async () => {
|
||||
if (!currentProject) return;
|
||||
|
||||
setIsSaving(true);
|
||||
try {
|
||||
const api = getElectronAPI();
|
||||
await api.writeFile(`${currentProject.path}/.automaker/app_spec.txt`, appSpec);
|
||||
setHasChanges(false);
|
||||
} catch (error) {
|
||||
logger.error('Failed to save spec:', error);
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
saveMutation.mutate(appSpec, {
|
||||
onSuccess: () => setHasChanges(false),
|
||||
});
|
||||
};
|
||||
|
||||
const handleChange = (value: string) => {
|
||||
@@ -31,7 +23,7 @@ export function useSpecSave() {
|
||||
};
|
||||
|
||||
return {
|
||||
isSaving,
|
||||
isSaving: saveMutation.isPending,
|
||||
hasChanges,
|
||||
setHasChanges,
|
||||
saveSpec,
|
||||
|
||||
Reference in New Issue
Block a user