mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-01 08:13:37 +00:00
Merge remote-tracking branch 'refs/remotes/origin/main'
# Conflicts: # apps/ui/src/routes/__root.tsx
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
export { useAutoMode } from './use-auto-mode';
|
||||
export { useBoardBackgroundSettings } from './use-board-background-settings';
|
||||
export { useElectronAgent } from './use-electron-agent';
|
||||
export { useGuidedPrompts } from './use-guided-prompts';
|
||||
export { useKeyboardShortcuts } from './use-keyboard-shortcuts';
|
||||
export { useMessageQueue } from './use-message-queue';
|
||||
export { useOSDetection, type OperatingSystem, type OSDetectionResult } from './use-os-detection';
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { useEffect, useCallback, useMemo } from 'react';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { createLogger } from '@automaker/utils/logger';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import type { AutoModeEvent } from '@/types/electron';
|
||||
|
||||
const logger = createLogger('AutoMode');
|
||||
|
||||
// Type guard for plan_approval_required event
|
||||
function isPlanApprovalEvent(
|
||||
event: AutoModeEvent
|
||||
@@ -67,7 +70,7 @@ export function useAutoMode() {
|
||||
if (!api?.autoMode) return;
|
||||
|
||||
const unsubscribe = api.autoMode.onEvent((event: AutoModeEvent) => {
|
||||
console.log('[AutoMode Event]', event);
|
||||
logger.info('Event:', event);
|
||||
|
||||
// Events include projectPath from backend - use it to look up project ID
|
||||
// Fall back to current projectId if not provided in event
|
||||
@@ -84,7 +87,7 @@ export function useAutoMode() {
|
||||
|
||||
// Skip event if we couldn't determine the project
|
||||
if (!eventProjectId) {
|
||||
console.warn('[AutoMode] Could not determine project for event:', event);
|
||||
logger.warn('Could not determine project for event:', event);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -103,7 +106,7 @@ export function useAutoMode() {
|
||||
case 'auto_mode_feature_complete':
|
||||
// Feature completed - remove from running tasks and UI will reload features on its own
|
||||
if (event.featureId) {
|
||||
console.log('[AutoMode] Feature completed:', event.featureId, 'passes:', event.passes);
|
||||
logger.info('Feature completed:', event.featureId, 'passes:', event.passes);
|
||||
removeRunningTask(eventProjectId, event.featureId);
|
||||
addAutoModeActivity({
|
||||
featureId: event.featureId,
|
||||
@@ -121,7 +124,7 @@ export function useAutoMode() {
|
||||
// Check if this is a user-initiated cancellation or abort (not a real error)
|
||||
if (event.errorType === 'cancellation' || event.errorType === 'abort') {
|
||||
// User cancelled/aborted the feature - just log as info, not an error
|
||||
console.log('[AutoMode] Feature cancelled/aborted:', event.error);
|
||||
logger.info('Feature cancelled/aborted:', event.error);
|
||||
// Remove from running tasks
|
||||
if (eventProjectId) {
|
||||
removeRunningTask(eventProjectId, event.featureId);
|
||||
@@ -130,7 +133,7 @@ export function useAutoMode() {
|
||||
}
|
||||
|
||||
// Real error - log and show to user
|
||||
console.error('[AutoMode Error]', event.error);
|
||||
logger.error('Error:', event.error);
|
||||
|
||||
// Check for authentication errors and provide a more helpful message
|
||||
const isAuthError =
|
||||
@@ -182,7 +185,7 @@ export function useAutoMode() {
|
||||
case 'auto_mode_phase':
|
||||
// Log phase transitions (Planning, Action, Verification)
|
||||
if (event.featureId && event.phase && event.message) {
|
||||
console.log(`[AutoMode] Phase: ${event.phase} for ${event.featureId}`);
|
||||
logger.debug(`[AutoMode] Phase: ${event.phase} for ${event.featureId}`);
|
||||
addAutoModeActivity({
|
||||
featureId: event.featureId,
|
||||
type: event.phase,
|
||||
@@ -195,7 +198,7 @@ export function useAutoMode() {
|
||||
case 'plan_approval_required':
|
||||
// Plan requires user approval before proceeding
|
||||
if (isPlanApprovalEvent(event)) {
|
||||
console.log(`[AutoMode] Plan approval required for ${event.featureId}`);
|
||||
logger.debug(`[AutoMode] Plan approval required for ${event.featureId}`);
|
||||
setPendingPlanApproval({
|
||||
featureId: event.featureId,
|
||||
projectPath: event.projectPath || currentProject?.path || '',
|
||||
@@ -208,7 +211,7 @@ export function useAutoMode() {
|
||||
case 'planning_started':
|
||||
// Log when planning phase begins
|
||||
if (event.featureId && event.mode && event.message) {
|
||||
console.log(`[AutoMode] Planning started (${event.mode}) for ${event.featureId}`);
|
||||
logger.debug(`[AutoMode] Planning started (${event.mode}) for ${event.featureId}`);
|
||||
addAutoModeActivity({
|
||||
featureId: event.featureId,
|
||||
type: 'planning',
|
||||
@@ -221,7 +224,7 @@ export function useAutoMode() {
|
||||
case 'plan_approved':
|
||||
// Log when plan is approved by user
|
||||
if (event.featureId) {
|
||||
console.log(`[AutoMode] Plan approved for ${event.featureId}`);
|
||||
logger.debug(`[AutoMode] Plan approved for ${event.featureId}`);
|
||||
addAutoModeActivity({
|
||||
featureId: event.featureId,
|
||||
type: 'action',
|
||||
@@ -236,7 +239,7 @@ export function useAutoMode() {
|
||||
case 'plan_auto_approved':
|
||||
// Log when plan is auto-approved (requirePlanApproval=false)
|
||||
if (event.featureId) {
|
||||
console.log(`[AutoMode] Plan auto-approved for ${event.featureId}`);
|
||||
logger.debug(`[AutoMode] Plan auto-approved for ${event.featureId}`);
|
||||
addAutoModeActivity({
|
||||
featureId: event.featureId,
|
||||
type: 'action',
|
||||
@@ -253,7 +256,7 @@ export function useAutoMode() {
|
||||
AutoModeEvent,
|
||||
{ type: 'plan_revision_requested' }
|
||||
>;
|
||||
console.log(
|
||||
logger.debug(
|
||||
`[AutoMode] Plan revision requested for ${event.featureId} (v${revisionEvent.planVersion})`
|
||||
);
|
||||
addAutoModeActivity({
|
||||
@@ -269,7 +272,7 @@ export function useAutoMode() {
|
||||
// Task started - show which task is being worked on
|
||||
if (event.featureId && 'taskId' in event && 'taskDescription' in event) {
|
||||
const taskEvent = event as Extract<AutoModeEvent, { type: 'auto_mode_task_started' }>;
|
||||
console.log(
|
||||
logger.debug(
|
||||
`[AutoMode] Task ${taskEvent.taskId} started for ${event.featureId}: ${taskEvent.taskDescription}`
|
||||
);
|
||||
addAutoModeActivity({
|
||||
@@ -284,7 +287,7 @@ export function useAutoMode() {
|
||||
// Task completed - show progress
|
||||
if (event.featureId && 'taskId' in event) {
|
||||
const taskEvent = event as Extract<AutoModeEvent, { type: 'auto_mode_task_complete' }>;
|
||||
console.log(
|
||||
logger.debug(
|
||||
`[AutoMode] Task ${taskEvent.taskId} completed for ${event.featureId} (${taskEvent.tasksCompleted}/${taskEvent.tasksTotal})`
|
||||
);
|
||||
addAutoModeActivity({
|
||||
@@ -302,7 +305,7 @@ export function useAutoMode() {
|
||||
AutoModeEvent,
|
||||
{ type: 'auto_mode_phase_complete' }
|
||||
>;
|
||||
console.log(
|
||||
logger.debug(
|
||||
`[AutoMode] Phase ${phaseEvent.phaseNumber} completed for ${event.featureId}`
|
||||
);
|
||||
addAutoModeActivity({
|
||||
@@ -330,18 +333,18 @@ export function useAutoMode() {
|
||||
// Start auto mode - UI only, feature pickup is handled in board-view.tsx
|
||||
const start = useCallback(() => {
|
||||
if (!currentProject) {
|
||||
console.error('No project selected');
|
||||
logger.error('No project selected');
|
||||
return;
|
||||
}
|
||||
|
||||
setAutoModeRunning(currentProject.id, true);
|
||||
console.log(`[AutoMode] Started with maxConcurrency: ${maxConcurrency}`);
|
||||
logger.debug(`[AutoMode] Started with maxConcurrency: ${maxConcurrency}`);
|
||||
}, [currentProject, setAutoModeRunning, maxConcurrency]);
|
||||
|
||||
// Stop auto mode - UI only, running tasks continue until natural completion
|
||||
const stop = useCallback(() => {
|
||||
if (!currentProject) {
|
||||
console.error('No project selected');
|
||||
logger.error('No project selected');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -350,14 +353,14 @@ export function useAutoMode() {
|
||||
// Stopping auto mode only turns off the toggle to prevent new features
|
||||
// from being picked up. Running tasks will complete naturally and be
|
||||
// removed via the auto_mode_feature_complete event.
|
||||
console.log('[AutoMode] Stopped - running tasks will continue');
|
||||
logger.info('Stopped - running tasks will continue');
|
||||
}, [currentProject, setAutoModeRunning]);
|
||||
|
||||
// Stop a specific feature
|
||||
const stopFeature = useCallback(
|
||||
async (featureId: string) => {
|
||||
if (!currentProject) {
|
||||
console.error('No project selected');
|
||||
logger.error('No project selected');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -371,7 +374,7 @@ export function useAutoMode() {
|
||||
|
||||
if (result.success) {
|
||||
removeRunningTask(currentProject.id, featureId);
|
||||
console.log('[AutoMode] Feature stopped successfully:', featureId);
|
||||
logger.info('Feature stopped successfully:', featureId);
|
||||
addAutoModeActivity({
|
||||
featureId,
|
||||
type: 'complete',
|
||||
@@ -379,11 +382,11 @@ export function useAutoMode() {
|
||||
passes: false,
|
||||
});
|
||||
} else {
|
||||
console.error('[AutoMode] Failed to stop feature:', result.error);
|
||||
logger.error('Failed to stop feature:', result.error);
|
||||
throw new Error(result.error || 'Failed to stop feature');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[AutoMode] Error stopping feature:', error);
|
||||
logger.error('Error stopping feature:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { useCallback } from 'react';
|
||||
import { createLogger } from '@automaker/utils/logger';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
import { getHttpApiClient } from '@/lib/http-api-client';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
const logger = createLogger('BoardBackground');
|
||||
|
||||
/**
|
||||
* Hook for managing board background settings with automatic persistence to server
|
||||
*/
|
||||
@@ -19,11 +22,11 @@ export function useBoardBackgroundSettings() {
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
console.error('Failed to persist settings:', result.error);
|
||||
logger.error('Failed to persist settings:', result.error);
|
||||
toast.error('Failed to save settings');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to persist settings:', error);
|
||||
logger.error('Failed to persist settings:', error);
|
||||
toast.error('Failed to save settings');
|
||||
}
|
||||
},
|
||||
|
||||
46
apps/ui/src/hooks/use-cursor-status-init.ts
Normal file
46
apps/ui/src/hooks/use-cursor-status-init.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useSetupStore } from '@/store/setup-store';
|
||||
import { getHttpApiClient } from '@/lib/http-api-client';
|
||||
|
||||
/**
|
||||
* Hook to initialize Cursor CLI status on app startup.
|
||||
* This ensures the cursorCliStatus is available in the setup store
|
||||
* before the user opens feature dialogs.
|
||||
*/
|
||||
export function useCursorStatusInit() {
|
||||
const { setCursorCliStatus, cursorCliStatus } = useSetupStore();
|
||||
const initialized = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Only initialize once per session
|
||||
if (initialized.current || cursorCliStatus !== null) {
|
||||
return;
|
||||
}
|
||||
initialized.current = true;
|
||||
|
||||
const initCursorStatus = async () => {
|
||||
try {
|
||||
const api = getHttpApiClient();
|
||||
const statusResult = await api.setup.getCursorStatus();
|
||||
|
||||
if (statusResult.success) {
|
||||
setCursorCliStatus({
|
||||
installed: statusResult.installed ?? false,
|
||||
version: statusResult.version ?? undefined,
|
||||
auth: statusResult.auth?.authenticated
|
||||
? {
|
||||
authenticated: true,
|
||||
method: statusResult.auth.method || 'unknown',
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// Silently fail - cursor is optional
|
||||
console.debug('[CursorStatusInit] Failed to check cursor status:', error);
|
||||
}
|
||||
};
|
||||
|
||||
initCursorStatus();
|
||||
}, [setCursorCliStatus, cursorCliStatus]);
|
||||
}
|
||||
@@ -4,11 +4,15 @@ import { useMessageQueue } from './use-message-queue';
|
||||
import type { ImageAttachment, TextFileAttachment } from '@/store/app-store';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import { sanitizeFilename } from '@/lib/image-utils';
|
||||
import { createLogger } from '@automaker/utils/logger';
|
||||
|
||||
const logger = createLogger('ElectronAgent');
|
||||
|
||||
interface UseElectronAgentOptions {
|
||||
sessionId: string;
|
||||
workingDirectory?: string;
|
||||
model?: string;
|
||||
thinkingLevel?: string;
|
||||
onToolUse?: (toolName: string, toolInput: unknown) => void;
|
||||
}
|
||||
|
||||
@@ -18,6 +22,7 @@ interface QueuedPrompt {
|
||||
message: string;
|
||||
imagePaths?: string[];
|
||||
model?: string;
|
||||
thinkingLevel?: string;
|
||||
addedAt: string;
|
||||
}
|
||||
|
||||
@@ -64,6 +69,7 @@ export function useElectronAgent({
|
||||
sessionId,
|
||||
workingDirectory,
|
||||
model,
|
||||
thinkingLevel,
|
||||
onToolUse,
|
||||
}: UseElectronAgentOptions): UseElectronAgentResult {
|
||||
const [messages, setMessages] = useState<Message[]>([]);
|
||||
@@ -91,7 +97,7 @@ export function useElectronAgent({
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
console.log('[useElectronAgent] Sending message directly', {
|
||||
logger.info('Sending message directly', {
|
||||
hasImages: images && images.length > 0,
|
||||
imageCount: images?.length || 0,
|
||||
hasTextFiles: textFiles && textFiles.length > 0,
|
||||
@@ -121,9 +127,9 @@ export function useElectronAgent({
|
||||
);
|
||||
if (result.success && result.path) {
|
||||
imagePaths.push(result.path);
|
||||
console.log('[useElectronAgent] Saved image to .automaker/images:', result.path);
|
||||
logger.info('Saved image to .automaker/images:', result.path);
|
||||
} else {
|
||||
console.error('[useElectronAgent] Failed to save image:', result.error);
|
||||
logger.error('Failed to save image:', result.error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,7 +139,8 @@ export function useElectronAgent({
|
||||
messageContent,
|
||||
workingDirectory,
|
||||
imagePaths,
|
||||
model
|
||||
model,
|
||||
thinkingLevel
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
@@ -143,13 +150,13 @@ export function useElectronAgent({
|
||||
// Note: We don't set isProcessing to false here because
|
||||
// it will be set by the "complete" or "error" stream event
|
||||
} catch (err) {
|
||||
console.error('[useElectronAgent] Failed to send message:', err);
|
||||
logger.error('Failed to send message:', err);
|
||||
setError(err instanceof Error ? err.message : 'Failed to send message');
|
||||
setIsProcessing(false);
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
[sessionId, workingDirectory, model, isProcessing]
|
||||
[sessionId, workingDirectory, model, thinkingLevel, isProcessing]
|
||||
);
|
||||
|
||||
// Message queue for queuing messages when agent is busy
|
||||
@@ -188,13 +195,13 @@ export function useElectronAgent({
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
console.log('[useElectronAgent] Starting session:', sessionId);
|
||||
logger.info('Starting session:', sessionId);
|
||||
const result = await api.agent!.start(sessionId, workingDirectory);
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
if (result.success && result.messages) {
|
||||
console.log('[useElectronAgent] Loaded', result.messages.length, 'messages');
|
||||
logger.info('Loaded', result.messages.length, 'messages');
|
||||
setMessages(result.messages);
|
||||
setIsConnected(true);
|
||||
|
||||
@@ -202,7 +209,7 @@ export function useElectronAgent({
|
||||
const historyResult = await api.agent!.getHistory(sessionId);
|
||||
if (mounted && historyResult.success) {
|
||||
const isRunning = historyResult.isRunning || false;
|
||||
console.log('[useElectronAgent] Session running state:', isRunning);
|
||||
logger.info('Session running state:', isRunning);
|
||||
setIsProcessing(isRunning);
|
||||
}
|
||||
} else {
|
||||
@@ -211,7 +218,7 @@ export function useElectronAgent({
|
||||
}
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
console.error('[useElectronAgent] Failed to initialize:', err);
|
||||
logger.error('Failed to initialize:', err);
|
||||
setError(err instanceof Error ? err.message : 'Failed to initialize');
|
||||
setIsProcessing(false);
|
||||
}
|
||||
@@ -227,7 +234,7 @@ export function useElectronAgent({
|
||||
// Auto-process queue when agent finishes processing
|
||||
useEffect(() => {
|
||||
if (!isProcessing && !isProcessingQueue && queuedMessages.length > 0) {
|
||||
console.log('[useElectronAgent] Auto-processing next queued message');
|
||||
logger.info('Auto-processing next queued message');
|
||||
processNext();
|
||||
}
|
||||
}, [isProcessing, isProcessingQueue, queuedMessages.length, processNext]);
|
||||
@@ -238,21 +245,21 @@ export function useElectronAgent({
|
||||
if (!api?.agent) return;
|
||||
if (!sessionId) return; // Don't subscribe if no session
|
||||
|
||||
console.log('[useElectronAgent] Subscribing to stream events for session:', sessionId);
|
||||
logger.info('Subscribing to stream events for session:', sessionId);
|
||||
|
||||
const handleStream = (event: StreamEvent) => {
|
||||
// CRITICAL: Only process events for our specific session
|
||||
if (event.sessionId !== sessionId) {
|
||||
console.log('[useElectronAgent] Ignoring event for different session:', event.sessionId);
|
||||
logger.info('Ignoring event for different session:', event.sessionId);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[useElectronAgent] Stream event for', sessionId, ':', event.type);
|
||||
logger.info('Stream event for', sessionId, ':', event.type);
|
||||
|
||||
switch (event.type) {
|
||||
case 'started':
|
||||
// Agent started processing (including from queue)
|
||||
console.log('[useElectronAgent] Agent started processing for session:', sessionId);
|
||||
logger.info('Agent started processing for session:', sessionId);
|
||||
setIsProcessing(true);
|
||||
break;
|
||||
|
||||
@@ -297,13 +304,13 @@ export function useElectronAgent({
|
||||
|
||||
case 'tool_use':
|
||||
// Tool being used
|
||||
console.log('[useElectronAgent] Tool use:', event.tool.name);
|
||||
logger.info('Tool use:', event.tool.name);
|
||||
onToolUse?.(event.tool.name, event.tool.input);
|
||||
break;
|
||||
|
||||
case 'complete':
|
||||
// Agent finished processing for THIS session
|
||||
console.log('[useElectronAgent] Processing complete for session:', sessionId);
|
||||
logger.info('Processing complete for session:', sessionId);
|
||||
setIsProcessing(false);
|
||||
if (event.messageId) {
|
||||
setMessages((prev) =>
|
||||
@@ -316,7 +323,7 @@ export function useElectronAgent({
|
||||
|
||||
case 'error':
|
||||
// Error occurred for THIS session
|
||||
console.error('[useElectronAgent] Agent error for session:', sessionId, event.error);
|
||||
logger.error('Agent error for session:', sessionId, event.error);
|
||||
setIsProcessing(false);
|
||||
setError(event.error);
|
||||
if (event.message) {
|
||||
@@ -327,13 +334,13 @@ export function useElectronAgent({
|
||||
|
||||
case 'queue_updated':
|
||||
// Server queue was updated
|
||||
console.log('[useElectronAgent] Queue updated:', event.queue);
|
||||
logger.info('Queue updated:', event.queue);
|
||||
setServerQueue(event.queue || []);
|
||||
break;
|
||||
|
||||
case 'queue_error':
|
||||
// Error processing a queued prompt
|
||||
console.error('[useElectronAgent] Queue error:', event.error);
|
||||
logger.error('Queue error:', event.error);
|
||||
setError(event.error);
|
||||
break;
|
||||
}
|
||||
@@ -343,7 +350,7 @@ export function useElectronAgent({
|
||||
|
||||
return () => {
|
||||
if (unsubscribeRef.current) {
|
||||
console.log('[useElectronAgent] Unsubscribing from stream events for session:', sessionId);
|
||||
logger.info('Unsubscribing from stream events for session:', sessionId);
|
||||
unsubscribeRef.current();
|
||||
unsubscribeRef.current = null;
|
||||
}
|
||||
@@ -360,7 +367,7 @@ export function useElectronAgent({
|
||||
}
|
||||
|
||||
if (isProcessing) {
|
||||
console.warn('[useElectronAgent] Already processing a message');
|
||||
logger.warn('Already processing a message');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -368,7 +375,7 @@ export function useElectronAgent({
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
console.log('[useElectronAgent] Sending message', {
|
||||
logger.info('Sending message', {
|
||||
hasImages: images && images.length > 0,
|
||||
imageCount: images?.length || 0,
|
||||
hasTextFiles: textFiles && textFiles.length > 0,
|
||||
@@ -398,9 +405,9 @@ export function useElectronAgent({
|
||||
);
|
||||
if (result.success && result.path) {
|
||||
imagePaths.push(result.path);
|
||||
console.log('[useElectronAgent] Saved image to .automaker/images:', result.path);
|
||||
logger.info('Saved image to .automaker/images:', result.path);
|
||||
} else {
|
||||
console.error('[useElectronAgent] Failed to save image:', result.error);
|
||||
logger.error('Failed to save image:', result.error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -410,7 +417,8 @@ export function useElectronAgent({
|
||||
messageContent,
|
||||
workingDirectory,
|
||||
imagePaths,
|
||||
model
|
||||
model,
|
||||
thinkingLevel
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
@@ -420,12 +428,12 @@ export function useElectronAgent({
|
||||
// Note: We don't set isProcessing to false here because
|
||||
// it will be set by the "complete" or "error" stream event
|
||||
} catch (err) {
|
||||
console.error('[useElectronAgent] Failed to send message:', err);
|
||||
logger.error('Failed to send message:', err);
|
||||
setError(err instanceof Error ? err.message : 'Failed to send message');
|
||||
setIsProcessing(false);
|
||||
}
|
||||
},
|
||||
[sessionId, workingDirectory, model, isProcessing]
|
||||
[sessionId, workingDirectory, model, thinkingLevel, isProcessing]
|
||||
);
|
||||
|
||||
// Stop current execution
|
||||
@@ -437,7 +445,7 @@ export function useElectronAgent({
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('[useElectronAgent] Stopping execution');
|
||||
logger.info('Stopping execution');
|
||||
const result = await api.agent!.stop(sessionId);
|
||||
|
||||
if (!result.success) {
|
||||
@@ -446,7 +454,7 @@ export function useElectronAgent({
|
||||
setIsProcessing(false);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[useElectronAgent] Failed to stop:', err);
|
||||
logger.error('Failed to stop:', err);
|
||||
setError(err instanceof Error ? err.message : 'Failed to stop execution');
|
||||
}
|
||||
}, [sessionId]);
|
||||
@@ -460,7 +468,7 @@ export function useElectronAgent({
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('[useElectronAgent] Clearing history');
|
||||
logger.info('Clearing history');
|
||||
const result = await api.agent!.clear(sessionId);
|
||||
|
||||
if (result.success) {
|
||||
@@ -470,7 +478,7 @@ export function useElectronAgent({
|
||||
setError(result.error || 'Failed to clear history');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[useElectronAgent] Failed to clear:', err);
|
||||
logger.error('Failed to clear:', err);
|
||||
setError(err instanceof Error ? err.message : 'Failed to clear history');
|
||||
}
|
||||
}, [sessionId]);
|
||||
@@ -512,18 +520,24 @@ export function useElectronAgent({
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[useElectronAgent] Adding to server queue');
|
||||
const result = await api.agent.queueAdd(sessionId, messageContent, imagePaths, model);
|
||||
logger.info('Adding to server queue');
|
||||
const result = await api.agent.queueAdd(
|
||||
sessionId,
|
||||
messageContent,
|
||||
imagePaths,
|
||||
model,
|
||||
thinkingLevel
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
setError(result.error || 'Failed to add to queue');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[useElectronAgent] Failed to add to queue:', err);
|
||||
logger.error('Failed to add to queue:', err);
|
||||
setError(err instanceof Error ? err.message : 'Failed to add to queue');
|
||||
}
|
||||
},
|
||||
[sessionId, workingDirectory, model]
|
||||
[sessionId, workingDirectory, model, thinkingLevel]
|
||||
);
|
||||
|
||||
// Remove a prompt from the server queue
|
||||
@@ -536,14 +550,14 @@ export function useElectronAgent({
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('[useElectronAgent] Removing from server queue:', promptId);
|
||||
logger.info('Removing from server queue:', promptId);
|
||||
const result = await api.agent.queueRemove(sessionId, promptId);
|
||||
|
||||
if (!result.success) {
|
||||
setError(result.error || 'Failed to remove from queue');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[useElectronAgent] Failed to remove from queue:', err);
|
||||
logger.error('Failed to remove from queue:', err);
|
||||
setError(err instanceof Error ? err.message : 'Failed to remove from queue');
|
||||
}
|
||||
},
|
||||
@@ -559,14 +573,14 @@ export function useElectronAgent({
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('[useElectronAgent] Clearing server queue');
|
||||
logger.info('Clearing server queue');
|
||||
const result = await api.agent.queueClear(sessionId);
|
||||
|
||||
if (!result.success) {
|
||||
setError(result.error || 'Failed to clear queue');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[useElectronAgent] Failed to clear queue:', err);
|
||||
logger.error('Failed to clear queue:', err);
|
||||
setError(err instanceof Error ? err.message : 'Failed to clear queue');
|
||||
}
|
||||
}, [sessionId]);
|
||||
|
||||
86
apps/ui/src/hooks/use-guided-prompts.ts
Normal file
86
apps/ui/src/hooks/use-guided-prompts.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Hook for fetching guided prompts from the backend API
|
||||
*
|
||||
* This hook provides the single source of truth for guided prompts,
|
||||
* fetched from the backend /api/ideation/prompts endpoint.
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import type { IdeationPrompt, PromptCategory, IdeaCategory } from '@automaker/types';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
|
||||
interface UseGuidedPromptsReturn {
|
||||
prompts: IdeationPrompt[];
|
||||
categories: PromptCategory[];
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
refetch: () => Promise<void>;
|
||||
getPromptsByCategory: (category: IdeaCategory) => IdeationPrompt[];
|
||||
getPromptById: (id: string) => IdeationPrompt | undefined;
|
||||
getCategoryById: (id: IdeaCategory) => PromptCategory | undefined;
|
||||
}
|
||||
|
||||
export function useGuidedPrompts(): UseGuidedPromptsReturn {
|
||||
const [prompts, setPrompts] = useState<IdeationPrompt[]>([]);
|
||||
const [categories, setCategories] = useState<PromptCategory[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const fetchPrompts = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const api = getElectronAPI();
|
||||
const result = await api.ideation?.getPrompts();
|
||||
|
||||
if (result?.success) {
|
||||
setPrompts(result.prompts || []);
|
||||
setCategories(result.categories || []);
|
||||
} else {
|
||||
setError(result?.error || 'Failed to fetch prompts');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch guided prompts:', err);
|
||||
setError(err instanceof Error ? err.message : 'Failed to fetch prompts');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchPrompts();
|
||||
}, [fetchPrompts]);
|
||||
|
||||
const getPromptsByCategory = useCallback(
|
||||
(category: IdeaCategory): IdeationPrompt[] => {
|
||||
return prompts.filter((p) => p.category === category);
|
||||
},
|
||||
[prompts]
|
||||
);
|
||||
|
||||
const getPromptById = useCallback(
|
||||
(id: string): IdeationPrompt | undefined => {
|
||||
return prompts.find((p) => p.id === id);
|
||||
},
|
||||
[prompts]
|
||||
);
|
||||
|
||||
const getCategoryById = useCallback(
|
||||
(id: IdeaCategory): PromptCategory | undefined => {
|
||||
return categories.find((c) => c.id === id);
|
||||
},
|
||||
[categories]
|
||||
);
|
||||
|
||||
return {
|
||||
prompts,
|
||||
categories,
|
||||
isLoading,
|
||||
error,
|
||||
refetch: fetchPrompts,
|
||||
getPromptsByCategory,
|
||||
getPromptById,
|
||||
getCategoryById,
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useCallback } from 'react';
|
||||
import { useAppStore, parseShortcut } from '@/store/app-store';
|
||||
import { useEffect, useCallback, useMemo } from 'react';
|
||||
import { useAppStore, parseShortcut, DEFAULT_KEYBOARD_SHORTCUTS } from '@/store/app-store';
|
||||
|
||||
export interface KeyboardShortcut {
|
||||
key: string; // Can be simple "K" or with modifiers "Shift+N", "Cmd+K"
|
||||
@@ -237,8 +237,18 @@ export function useKeyboardShortcuts(shortcuts: KeyboardShortcut[]) {
|
||||
/**
|
||||
* Hook to get current keyboard shortcuts from store
|
||||
* This replaces the static constants and allows customization
|
||||
* Merges with defaults to ensure new shortcuts are always available
|
||||
*/
|
||||
export function useKeyboardShortcutsConfig() {
|
||||
const keyboardShortcuts = useAppStore((state) => state.keyboardShortcuts);
|
||||
return keyboardShortcuts;
|
||||
|
||||
// Merge with defaults to ensure new shortcuts are available
|
||||
// even if user's persisted state predates them
|
||||
return useMemo(
|
||||
() => ({
|
||||
...DEFAULT_KEYBOARD_SHORTCUTS,
|
||||
...keyboardShortcuts,
|
||||
}),
|
||||
[keyboardShortcuts]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { createLogger } from '@automaker/utils/logger';
|
||||
import type { ImageAttachment, TextFileAttachment } from '@/store/app-store';
|
||||
|
||||
const logger = createLogger('MessageQueue');
|
||||
|
||||
export interface QueuedMessage {
|
||||
id: string;
|
||||
content: string;
|
||||
@@ -72,7 +75,7 @@ export function useMessageQueue({ onProcessNext }: UseMessageQueueOptions): UseM
|
||||
// Remove the processed message from queue
|
||||
setQueuedMessages((prev) => prev.slice(1));
|
||||
} catch (error) {
|
||||
console.error('Error processing queued message:', error);
|
||||
logger.error('Error processing queued message:', error);
|
||||
// Keep the message in queue for retry or manual removal
|
||||
} finally {
|
||||
setIsProcessingQueue(false);
|
||||
|
||||
@@ -18,11 +18,14 @@
|
||||
*/
|
||||
|
||||
import { useEffect, useState, useRef } from 'react';
|
||||
import { createLogger } from '@automaker/utils/logger';
|
||||
import { getHttpApiClient, waitForApiKeyInit } from '@/lib/http-api-client';
|
||||
import { isElectron } from '@/lib/electron';
|
||||
import { getItem, removeItem } from '@/lib/storage';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
|
||||
const logger = createLogger('SettingsMigration');
|
||||
|
||||
/**
|
||||
* State returned by useSettingsMigration hook
|
||||
*/
|
||||
@@ -109,7 +112,7 @@ export function useSettingsMigration(): MigrationState {
|
||||
const status = await api.settings.getStatus();
|
||||
|
||||
if (!status.success) {
|
||||
console.error('[Settings Migration] Failed to get status:', status);
|
||||
logger.error('Failed to get status:', status);
|
||||
setState({
|
||||
checked: true,
|
||||
migrated: false,
|
||||
@@ -120,7 +123,7 @@ export function useSettingsMigration(): MigrationState {
|
||||
|
||||
// If settings files already exist, no migration needed
|
||||
if (!status.needsMigration) {
|
||||
console.log('[Settings Migration] Settings files exist, no migration needed');
|
||||
logger.info('Settings files exist, no migration needed');
|
||||
setState({ checked: true, migrated: false, error: null });
|
||||
return;
|
||||
}
|
||||
@@ -128,12 +131,12 @@ export function useSettingsMigration(): MigrationState {
|
||||
// Check if we have localStorage data to migrate
|
||||
const automakerStorage = getItem('automaker-storage');
|
||||
if (!automakerStorage) {
|
||||
console.log('[Settings Migration] No localStorage data to migrate');
|
||||
logger.info('No localStorage data to migrate');
|
||||
setState({ checked: true, migrated: false, error: null });
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[Settings Migration] Starting migration...');
|
||||
logger.info('Starting migration...');
|
||||
|
||||
// Collect all localStorage data
|
||||
const localStorageData: Record<string, string> = {};
|
||||
@@ -148,7 +151,7 @@ export function useSettingsMigration(): MigrationState {
|
||||
const result = await api.settings.migrate(localStorageData);
|
||||
|
||||
if (result.success) {
|
||||
console.log('[Settings Migration] Migration successful:', {
|
||||
logger.info('Migration successful:', {
|
||||
globalSettings: result.migratedGlobalSettings,
|
||||
credentials: result.migratedCredentials,
|
||||
projects: result.migratedProjectCount,
|
||||
@@ -161,7 +164,7 @@ export function useSettingsMigration(): MigrationState {
|
||||
|
||||
setState({ checked: true, migrated: true, error: null });
|
||||
} else {
|
||||
console.warn('[Settings Migration] Migration had errors:', result.errors);
|
||||
logger.warn('Migration had errors:', result.errors);
|
||||
setState({
|
||||
checked: true,
|
||||
migrated: false,
|
||||
@@ -169,7 +172,7 @@ export function useSettingsMigration(): MigrationState {
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Settings Migration] Migration failed:', error);
|
||||
logger.error('Migration failed:', error);
|
||||
setState({
|
||||
checked: true,
|
||||
migrated: false,
|
||||
@@ -224,14 +227,13 @@ export async function syncSettingsToServer(): Promise<boolean> {
|
||||
muteDoneSound: state.muteDoneSound,
|
||||
enhancementModel: state.enhancementModel,
|
||||
validationModel: state.validationModel,
|
||||
phaseModels: state.phaseModels,
|
||||
autoLoadClaudeMd: state.autoLoadClaudeMd,
|
||||
enableSandboxMode: state.enableSandboxMode,
|
||||
skipSandboxWarning: state.skipSandboxWarning,
|
||||
keyboardShortcuts: state.keyboardShortcuts,
|
||||
aiProfiles: state.aiProfiles,
|
||||
mcpServers: state.mcpServers,
|
||||
mcpAutoApproveTools: state.mcpAutoApproveTools,
|
||||
mcpUnrestrictedTools: state.mcpUnrestrictedTools,
|
||||
promptCustomization: state.promptCustomization,
|
||||
projects: state.projects,
|
||||
trashedProjects: state.trashedProjects,
|
||||
@@ -243,7 +245,7 @@ export async function syncSettingsToServer(): Promise<boolean> {
|
||||
const result = await api.settings.updateGlobal(updates);
|
||||
return result.success;
|
||||
} catch (error) {
|
||||
console.error('[Settings Sync] Failed to sync settings:', error);
|
||||
logger.error('Failed to sync settings:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -270,7 +272,7 @@ export async function syncCredentialsToServer(apiKeys: {
|
||||
const result = await api.settings.updateCredentials({ apiKeys });
|
||||
return result.success;
|
||||
} catch (error) {
|
||||
console.error('[Settings Sync] Failed to sync credentials:', error);
|
||||
logger.error('Failed to sync credentials:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -311,7 +313,7 @@ export async function syncProjectSettingsToServer(
|
||||
const result = await api.settings.updateProject(projectPath, updates);
|
||||
return result.success;
|
||||
} catch (error) {
|
||||
console.error('[Settings Sync] Failed to sync project settings:', error);
|
||||
logger.error('Failed to sync project settings:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -331,22 +333,20 @@ export async function loadMCPServersFromServer(): Promise<boolean> {
|
||||
const result = await api.settings.getGlobal();
|
||||
|
||||
if (!result.success || !result.settings) {
|
||||
console.error('[Settings Load] Failed to load settings:', result.error);
|
||||
logger.error('Failed to load settings:', result.error);
|
||||
return false;
|
||||
}
|
||||
|
||||
const mcpServers = result.settings.mcpServers || [];
|
||||
const mcpAutoApproveTools = result.settings.mcpAutoApproveTools ?? true;
|
||||
const mcpUnrestrictedTools = result.settings.mcpUnrestrictedTools ?? true;
|
||||
|
||||
// Clear existing and add all from server
|
||||
// We need to update the store directly since we can't use hooks here
|
||||
useAppStore.setState({ mcpServers, mcpAutoApproveTools, mcpUnrestrictedTools });
|
||||
useAppStore.setState({ mcpServers });
|
||||
|
||||
console.log(`[Settings Load] Loaded ${mcpServers.length} MCP servers from server`);
|
||||
logger.info(`Loaded ${mcpServers.length} MCP servers from server`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('[Settings Load] Failed to load MCP servers:', error);
|
||||
logger.error('Failed to load MCP servers:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user