Initial working version of automation support

This commit is contained in:
James Brunton
2025-09-05 16:05:18 +01:00
parent b9b8e6e4e1
commit e929d7e349
11 changed files with 990 additions and 49 deletions

View File

@@ -15,9 +15,11 @@ export function useAutomateOperation() {
throw new Error('No automation configuration provided');
}
// Execute the automation sequence and return the final results
console.log('🔍 Full automation config:', params.automationConfig);
// Execute the automation sequence using the regular executor
const finalResults = await executeAutomationSequence(
params.automationConfig!,
params.automationConfig,
files,
toolRegistry,
(stepIndex: number, operationName: string) => {
@@ -31,7 +33,6 @@ export function useAutomateOperation() {
(stepIndex: number, error: string) => {
console.error(`Step ${stepIndex + 1} failed:`, error);
params.onStepError?.(stepIndex, error);
throw new Error(`Automation step ${stepIndex + 1} failed: ${error}`);
}
);

View File

@@ -0,0 +1,79 @@
import { useCallback } from 'react';
import { ToolRegistry } from '../../../data/toolsTaxonomy';
import { AutomationTool } from '../../../types/automation';
import { ToolDefinition } from '../../../components/tools/shared/toolDefinition';
export function useAutomationExecutor(toolRegistry: ToolRegistry) {
const executeStep = useCallback(async (tool: AutomationTool, files: File[]): Promise<File[]> => {
const toolEntry = toolRegistry[tool.operation as keyof ToolRegistry];
if (!toolEntry) {
throw new Error(`Tool ${tool.operation} not found in registry`);
}
// Handle definition-based tools
if (toolEntry.definition) {
const definition = toolEntry.definition as ToolDefinition<unknown>;
console.log(`🎯 Using definition-based tool: ${definition.id}`);
const operation = definition.useOperation();
const result = await operation.executeOperation(tool.parameters, files);
if (!result.success) {
throw new Error(result.error || 'Operation failed');
}
console.log(`✅ Definition-based tool returned ${result.files.length} files`);
return result.files;
}
// Handle legacy tools with operationConfig
if (toolEntry.operationConfig) {
// Import the legacy executor function and use it
const { executeToolOperationWithPrefix } = await import('../../../utils/automationExecutor');
return executeToolOperationWithPrefix(
tool.operation,
tool.parameters || {},
files,
toolRegistry
);
}
throw new Error(`Tool ${tool.operation} has no execution method available`);
}, [toolRegistry]);
const executeSequence = useCallback(async (
tools: AutomationTool[],
initialFiles: File[],
onStepStart?: (stepIndex: number, operationName: string) => void,
onStepComplete?: (stepIndex: number, resultFiles: File[]) => void,
onStepError?: (stepIndex: number, error: string) => void
): Promise<File[]> => {
let currentFiles = initialFiles;
for (let i = 0; i < tools.length; i++) {
const tool = tools[i];
try {
onStepStart?.(i, tool.operation);
console.log(`🔄 Executing step ${i + 1}/${tools.length}: ${tool.operation}`);
const resultFiles = await executeStep(tool, currentFiles);
currentFiles = resultFiles;
onStepComplete?.(i, resultFiles);
console.log(`✅ Step ${i + 1} completed with ${resultFiles.length} files`);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
console.error(`❌ Step ${i + 1} failed:`, error);
onStepError?.(i, errorMessage);
throw new Error(`Step ${i + 1} failed: ${errorMessage}`);
}
}
return currentFiles;
}, [executeStep]);
return {
executeStep,
executeSequence
};
}

View File

@@ -0,0 +1,159 @@
import { useState, useEffect, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { AutomationTool, AutomationConfig, AutomationMode } from '../../../types/automation';
import { AUTOMATION_CONSTANTS } from '../../../constants/automation';
import { ToolRegistry } from '../../../data/toolsTaxonomy';
import { ToolDefinition } from '../../../components/tools/shared/toolDefinition';
interface UseEnhancedAutomationFormProps {
mode: AutomationMode;
existingAutomation?: AutomationConfig;
toolRegistry: ToolRegistry;
}
/**
* Enhanced automation form hook that works with both definition-based and legacy tools
*/
export function useEnhancedAutomationForm({ mode, existingAutomation, toolRegistry }: UseEnhancedAutomationFormProps) {
const { t } = useTranslation();
const [automationName, setAutomationName] = useState('');
const [automationDescription, setAutomationDescription] = useState('');
const [automationIcon, setAutomationIcon] = useState<string>('');
const [selectedTools, setSelectedTools] = useState<AutomationTool[]>([]);
const getToolName = useCallback((operation: string) => {
const tool = toolRegistry?.[operation as keyof ToolRegistry] as any;
return tool?.name || t(`tools.${operation}.name`, operation);
}, [toolRegistry, t]);
const getToolDefaultParameters = useCallback((operation: string): Record<string, unknown> => {
const toolEntry = toolRegistry[operation as keyof ToolRegistry];
if (!toolEntry) return {};
// Check if it's a definition-based tool
if (toolEntry.definition) {
const definition = toolEntry.definition as ToolDefinition<unknown>;
// For definition-based tools, we need to get defaults from the parameters hook
// This is tricky because we can't call hooks here, but we can provide sensible defaults
// TODO: Consider creating a static defaultParameters method on definitions
// For now, return empty object - the definition components should handle their own defaults
return {};
}
// Legacy operationConfig approach
const config = toolEntry.operationConfig;
if (config?.defaultParameters) {
return { ...config.defaultParameters };
}
return {};
}, [toolRegistry]);
/**
* Get list of automatable tools from the registry
* Includes both definition-based and legacy tools with settingsComponent
*/
const getAutomatableTools = useCallback(() => {
return Object.entries(toolRegistry)
.filter(([_, toolEntry]) => {
// Include definition-based tools OR legacy tools with settings
return toolEntry.definition || toolEntry.settingsComponent;
})
.map(([toolId, toolEntry]) => ({
id: toolId,
name: toolEntry.name,
hasDefinition: !!toolEntry.definition,
hasLegacySettings: !!toolEntry.settingsComponent
}));
}, [toolRegistry]);
/**
* Check if a tool supports automation
*/
const isToolAutomatable = useCallback((operation: string) => {
const toolEntry = toolRegistry[operation as keyof ToolRegistry];
return !!(toolEntry?.definition || toolEntry?.settingsComponent);
}, [toolRegistry]);
// Initialize based on mode and existing automation
useEffect(() => {
if ((mode === AutomationMode.SUGGESTED || mode === AutomationMode.EDIT) && existingAutomation) {
setAutomationName(existingAutomation.name || '');
setAutomationDescription(existingAutomation.description || '');
setAutomationIcon(existingAutomation.icon || '');
const operations = existingAutomation.operations || [];
const tools = operations.map((op, index) => {
const operation = typeof op === 'string' ? op : op.operation;
return {
id: `${operation}-${Date.now()}-${index}`,
operation: operation,
name: getToolName(operation),
configured: mode === AutomationMode.EDIT ? true : false,
parameters: typeof op === 'object' ? op.parameters || {} : {}
};
});
setSelectedTools(tools);
} else {
// Creating new automation
setAutomationName('');
setAutomationDescription('');
setAutomationIcon('');
setSelectedTools([]);
}
}, [mode, existingAutomation, getToolName]);
const addTool = useCallback((operation: string) => {
if (!isToolAutomatable(operation)) {
console.warn(`Tool ${operation} is not automatable`);
return;
}
const newTool: AutomationTool = {
id: `${operation}-${Date.now()}`,
operation,
name: getToolName(operation),
configured: false,
parameters: getToolDefaultParameters(operation)
};
setSelectedTools(prev => [...prev, newTool]);
}, [getToolName, getToolDefaultParameters, isToolAutomatable]);
const removeTool = useCallback((toolId: string) => {
setSelectedTools(prev => prev.filter(tool => tool.id !== toolId));
}, []);
const updateTool = useCallback((toolId: string, updates: Partial<AutomationTool>) => {
setSelectedTools(prev => prev.map(tool =>
tool.id === toolId ? { ...tool, ...updates } : tool
));
}, []);
const hasUnsavedChanges = selectedTools.length > 0 || automationName.trim() !== '';
const canSaveAutomation = automationName.trim() !== '' &&
selectedTools.length > 0 &&
selectedTools.every(tool => tool.configured);
return {
automationName,
setAutomationName,
automationDescription,
setAutomationDescription,
automationIcon,
setAutomationIcon,
selectedTools,
addTool,
removeTool,
updateTool,
hasUnsavedChanges,
canSaveAutomation,
getToolName,
getToolDefaultParameters,
getAutomatableTools,
isToolAutomatable
};
}