First automation run

This commit is contained in:
Connor Yoh 2025-08-20 20:28:17 +01:00
parent 1136c00547
commit fa5baf9371
22 changed files with 644 additions and 63 deletions

View File

@ -257,12 +257,24 @@ export default function AutomationCreation({ mode, existingAutomation, onBack, o
key={`tool-selector-${tool.id}`} key={`tool-selector-${tool.id}`}
onSelect={(newOperation) => { onSelect={(newOperation) => {
const updatedTools = [...selectedTools]; const updatedTools = [...selectedTools];
// Get default parameters from the tool
let defaultParams = {};
const tool = toolRegistry?.[newOperation];
if (tool?.component && (tool.component as any).getDefaultParameters) {
try {
defaultParams = (tool.component as any).getDefaultParameters();
} catch (error) {
console.warn(`Failed to get default parameters for ${newOperation}:`, error);
}
}
updatedTools[index] = { updatedTools[index] = {
...updatedTools[index], ...updatedTools[index],
operation: newOperation, operation: newOperation,
name: getToolName(newOperation), name: getToolName(newOperation),
configured: false, configured: false,
parameters: {} parameters: defaultParams
}; };
setSelectedTools(updatedTools); setSelectedTools(updatedTools);
}} }}

View File

@ -0,0 +1,112 @@
import React, { useEffect, useState } from 'react';
import { useFlatToolRegistry } from '../../../data/useTranslatedToolRegistry';
import { ToolComponent } from '../../../types/tool';
interface AutomationExecutorProps {
automation: any;
files: File[];
onStepStart: (stepIndex: number) => void;
onStepComplete: (stepIndex: number, results: File[]) => void;
onStepError: (stepIndex: number, error: string) => void;
onComplete: (finalResults: File[]) => void;
shouldExecute: boolean;
}
/**
* Component that manages the execution of automation steps using real tool hooks.
* This component creates operation hook instances for each tool in the automation.
*/
export const AutomationExecutor: React.FC<AutomationExecutorProps> = ({
automation,
files,
onStepStart,
onStepComplete,
onStepError,
onComplete,
shouldExecute
}) => {
const toolRegistry = useFlatToolRegistry();
const [currentStepIndex, setCurrentStepIndex] = useState(-1);
const [currentFiles, setCurrentFiles] = useState<File[]>(files);
const [isExecuting, setIsExecuting] = useState(false);
// Create operation hooks for all tools in the automation
const operationHooks = React.useMemo(() => {
if (!automation?.operations) return {};
const hooks: Record<string, any> = {};
automation.operations.forEach((op: any, index: number) => {
const tool = toolRegistry[op.operation];
if (tool?.component) {
const toolComponent = tool.component as ToolComponent;
if (toolComponent.tool) {
// We still can't call the hook here dynamically
// This approach also won't work
}
}
});
return hooks;
}, [automation, toolRegistry]);
// Execute automation when shouldExecute becomes true
useEffect(() => {
if (shouldExecute && !isExecuting && automation?.operations?.length > 0) {
executeAutomation();
}
}, [shouldExecute, isExecuting, automation]);
const executeAutomation = async () => {
if (!automation?.operations || automation.operations.length === 0) {
return;
}
setIsExecuting(true);
setCurrentFiles(files);
let filesToProcess = [...files];
try {
for (let i = 0; i < automation.operations.length; i++) {
setCurrentStepIndex(i);
const operation = automation.operations[i];
onStepStart(i);
// Get the tool
const tool = toolRegistry[operation.operation];
if (!tool?.component) {
throw new Error(`Tool not found: ${operation.operation}`);
}
const toolComponent = tool.component as ToolComponent;
if (!toolComponent.tool) {
throw new Error(`Tool ${operation.operation} does not support automation`);
}
// For now, simulate the execution
// TODO: We need to find a way to actually execute the tool operation
await new Promise(resolve => setTimeout(resolve, 2000));
// For now, assume the operation succeeded with the same files
const resultFiles = filesToProcess; // TODO: Get actual results
onStepComplete(i, resultFiles);
filesToProcess = resultFiles;
setCurrentFiles(resultFiles);
}
onComplete(filesToProcess);
setIsExecuting(false);
setCurrentStepIndex(-1);
} catch (error: any) {
console.error('Automation execution failed:', error);
onStepError(currentStepIndex, error.message);
setIsExecuting(false);
setCurrentStepIndex(-1);
}
};
// This component doesn't render anything visible
return null;
};

View File

@ -16,11 +16,14 @@ import PlayArrowIcon from '@mui/icons-material/PlayArrow';
import CheckIcon from '@mui/icons-material/Check'; import CheckIcon from '@mui/icons-material/Check';
import ErrorIcon from '@mui/icons-material/Error'; import ErrorIcon from '@mui/icons-material/Error';
import { useFileContext } from '../../../contexts/FileContext'; import { useFileContext } from '../../../contexts/FileContext';
import { useFlatToolRegistry } from '../../../data/useTranslatedToolRegistry';
import { executeAutomationSequence } from '../../../utils/automationExecutor';
interface AutomationRunProps { interface AutomationRunProps {
automation: any; automation: any;
onBack: () => void; onBack: () => void;
onComplete: () => void; onComplete: () => void;
automateOperation?: any; // Add the operation hook to store results
} }
interface ExecutionStep { interface ExecutionStep {
@ -31,25 +34,34 @@ interface ExecutionStep {
error?: string; error?: string;
} }
export default function AutomationRun({ automation, onBack, onComplete }: AutomationRunProps) { export default function AutomationRun({ automation, onBack, onComplete, automateOperation }: AutomationRunProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const { activeFiles } = useFileContext(); const { activeFiles, consumeFiles } = useFileContext();
const toolRegistry = useFlatToolRegistry();
const [isExecuting, setIsExecuting] = useState(false); const [isExecuting, setIsExecuting] = useState(false);
const [executionSteps, setExecutionSteps] = useState<ExecutionStep[]>([]); const [executionSteps, setExecutionSteps] = useState<ExecutionStep[]>([]);
const [currentStepIndex, setCurrentStepIndex] = useState(-1); const [currentStepIndex, setCurrentStepIndex] = useState(-1);
const [currentFiles, setCurrentFiles] = useState<File[]>([]);
// Initialize execution steps from automation // Initialize execution steps from automation
React.useEffect(() => { React.useEffect(() => {
if (automation?.operations) { if (automation?.operations) {
const steps = automation.operations.map((op: any, index: number) => ({ const steps = automation.operations.map((op: any, index: number) => {
const tool = toolRegistry[op.operation];
return {
id: `${op.operation}-${index}`, id: `${op.operation}-${index}`,
operation: op.operation, operation: op.operation,
name: op.operation, // You might want to get the display name from tool registry name: tool?.name || op.operation,
status: 'pending' as const status: 'pending' as const
})); };
});
setExecutionSteps(steps); setExecutionSteps(steps);
} }
}, [automation]); // Initialize current files with active files
if (activeFiles) {
setCurrentFiles([...activeFiles]);
}
}, [automation, toolRegistry, activeFiles]);
const executeAutomation = async () => { const executeAutomation = async () => {
if (!activeFiles || activeFiles.length === 0) { if (!activeFiles || activeFiles.length === 0) {
@ -61,33 +73,52 @@ export default function AutomationRun({ automation, onBack, onComplete }: Automa
setCurrentStepIndex(0); setCurrentStepIndex(0);
try { try {
for (let i = 0; i < executionSteps.length; i++) { // Execute the automation sequence using the new executor
setCurrentStepIndex(i); const finalResults = await executeAutomationSequence(
automation,
// Update step status to running activeFiles,
(stepIndex: number, operationName: string) => {
// Step started
setCurrentStepIndex(stepIndex);
setExecutionSteps(prev => prev.map((step, idx) => setExecutionSteps(prev => prev.map((step, idx) =>
idx === i ? { ...step, status: 'running' } : step idx === stepIndex ? { ...step, status: 'running' } : step
)); ));
},
// Simulate step execution (replace with actual tool execution) (stepIndex: number, resultFiles: File[]) => {
await new Promise(resolve => setTimeout(resolve, 2000)); // Step completed
// Update step status to completed
setExecutionSteps(prev => prev.map((step, idx) => setExecutionSteps(prev => prev.map((step, idx) =>
idx === i ? { ...step, status: 'completed' } : step idx === stepIndex ? { ...step, status: 'completed' } : step
));
setCurrentFiles(resultFiles);
},
(stepIndex: number, error: string) => {
// Step failed
setExecutionSteps(prev => prev.map((step, idx) =>
idx === stepIndex ? { ...step, status: 'error', error } : step
)); ));
} }
);
// All steps completed successfully
setCurrentStepIndex(-1); setCurrentStepIndex(-1);
setIsExecuting(false); setIsExecuting(false);
setCurrentFiles(finalResults);
// All steps completed - show success // Properly integrate results with FileContext
} catch (error) { if (finalResults.length > 0) {
// Handle error console.log(`🎨 Integrating ${finalResults.length} result files with FileContext`);
setExecutionSteps(prev => prev.map((step, idx) =>
idx === currentStepIndex ? { ...step, status: 'error', error: error?.toString() } : step // Use FileContext's consumeFiles to properly add results
)); // This replaces input files with output files (like other tools do)
await consumeFiles(activeFiles, finalResults);
console.log(`✅ Successfully integrated automation results with FileContext`);
}
} catch (error: any) {
console.error('Automation execution failed:', error);
setIsExecuting(false); setIsExecuting(false);
setCurrentStepIndex(-1);
} }
}; };

View File

@ -1,5 +1,6 @@
import { type TFunction } from 'i18next'; import { type TFunction } from 'i18next';
import React from 'react'; import React from 'react';
import { ToolOperationHook } from '../hooks/tools/shared/useToolOperation';
export enum SubcategoryId { export enum SubcategoryId {
SIGNING = 'signing', SIGNING = 'signing',
@ -34,6 +35,8 @@ export type ToolRegistryEntry = {
endpoints?: string[]; endpoints?: string[];
link?: string; link?: string;
type?: string; type?: string;
// Hook for automation execution
operationHook?: () => ToolOperationHook<any>;
} }
export type ToolRegistry = Record<string, ToolRegistryEntry>; export type ToolRegistry = Record<string, ToolRegistryEntry>;

View File

@ -0,0 +1,141 @@
import { useState, useCallback } from 'react';
import { useFlatToolRegistry } from '../../../data/useTranslatedToolRegistry';
import { ToolComponent } from '../../../types/tool';
interface ExecutionStep {
id: string;
operation: string;
name: string;
status: 'pending' | 'running' | 'completed' | 'error';
error?: string;
parameters?: any;
}
interface AutomationExecutionState {
isExecuting: boolean;
currentStepIndex: number;
executionSteps: ExecutionStep[];
currentFiles: File[];
}
/**
* Hook for managing automation execution with real tool operations
*/
export const useAutomationExecution = () => {
const toolRegistry = useFlatToolRegistry();
const [state, setState] = useState<AutomationExecutionState>({
isExecuting: false,
currentStepIndex: -1,
executionSteps: [],
currentFiles: []
});
// Store operation hook instances for the current automation
const [operationHooks, setOperationHooks] = useState<Record<string, any>>({});
const initializeAutomation = useCallback((automation: any, initialFiles: File[]) => {
if (!automation?.operations) return;
const steps = automation.operations.map((op: any, index: number) => {
const tool = toolRegistry[op.operation];
return {
id: `${op.operation}-${index}`,
operation: op.operation,
name: tool?.name || op.operation,
status: 'pending' as const,
parameters: op.parameters || {}
};
});
// Initialize operation hooks for all tools in the automation
const hooks: Record<string, any> = {};
steps.forEach((step) => {
const tool = toolRegistry[step.operation];
if (tool?.component) {
const toolComponent = tool.component as ToolComponent;
if (toolComponent.tool) {
const hookFactory = toolComponent.tool();
// We still can't call hooks here - this approach won't work
}
}
});
setState({
isExecuting: false,
currentStepIndex: -1,
executionSteps: steps,
currentFiles: [...initialFiles]
});
}, [toolRegistry]);
const executeAutomation = useCallback(async () => {
if (state.executionSteps.length === 0 || state.currentFiles.length === 0) {
throw new Error('No steps or files to execute');
}
setState(prev => ({ ...prev, isExecuting: true, currentStepIndex: 0 }));
let filesToProcess = [...state.currentFiles];
try {
for (let i = 0; i < state.executionSteps.length; i++) {
setState(prev => ({ ...prev, currentStepIndex: i }));
const step = state.executionSteps[i];
// Update step status to running
setState(prev => ({
...prev,
executionSteps: prev.executionSteps.map((s, idx) =>
idx === i ? { ...s, status: 'running' } : s
)
}));
// Get the tool and validate it supports automation
const tool = toolRegistry[step.operation];
if (!tool?.component) {
throw new Error(`Tool not found: ${step.operation}`);
}
const toolComponent = tool.component as ToolComponent;
if (!toolComponent.tool) {
throw new Error(`Tool ${step.operation} does not support automation`);
}
// For now, simulate execution until we solve the hook problem
await new Promise(resolve => setTimeout(resolve, 2000));
// Update step status to completed
setState(prev => ({
...prev,
executionSteps: prev.executionSteps.map((s, idx) =>
idx === i ? { ...s, status: 'completed' } : s
)
}));
// TODO: Update filesToProcess with actual results
}
setState(prev => ({
...prev,
isExecuting: false,
currentStepIndex: -1,
currentFiles: filesToProcess
}));
} catch (error: any) {
console.error('Automation execution failed:', error);
setState(prev => ({
...prev,
isExecuting: false,
executionSteps: prev.executionSteps.map((s, idx) =>
idx === prev.currentStepIndex ? { ...s, status: 'error', error: error.message } : s
)
}));
}
}, [state.executionSteps, state.currentFiles, toolRegistry]);
return {
...state,
initializeAutomation,
executeAutomation
};
};

View File

@ -9,7 +9,7 @@ export interface CompressParametersHook {
getEndpointName: () => string; getEndpointName: () => string;
} }
const initialParameters: CompressParameters = { export const initialParameters: CompressParameters = {
compressionLevel: 5, compressionLevel: 5,
grayscale: false, grayscale: false,
expectedSize: '', expectedSize: '',

View File

@ -9,11 +9,11 @@ import { createToolFlow } from "../components/tools/shared/createToolFlow";
import AddPasswordSettings from "../components/tools/addPassword/AddPasswordSettings"; import AddPasswordSettings from "../components/tools/addPassword/AddPasswordSettings";
import ChangePermissionsSettings from "../components/tools/changePermissions/ChangePermissionsSettings"; import ChangePermissionsSettings from "../components/tools/changePermissions/ChangePermissionsSettings";
import { useAddPasswordParameters } from "../hooks/tools/addPassword/useAddPasswordParameters"; import { useAddPasswordParameters, defaultParameters } from "../hooks/tools/addPassword/useAddPasswordParameters";
import { useAddPasswordOperation } from "../hooks/tools/addPassword/useAddPasswordOperation"; import { useAddPasswordOperation } from "../hooks/tools/addPassword/useAddPasswordOperation";
import { useAddPasswordTips } from "../components/tooltips/useAddPasswordTips"; import { useAddPasswordTips } from "../components/tooltips/useAddPasswordTips";
import { useAddPasswordPermissionsTips } from "../components/tooltips/useAddPasswordPermissionsTips"; import { useAddPasswordPermissionsTips } from "../components/tooltips/useAddPasswordPermissionsTips";
import { BaseToolProps } from "../types/tool"; import { BaseToolProps, ToolComponent } from "../types/tool";
const AddPassword = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => { const AddPassword = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -114,4 +114,12 @@ const AddPassword = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
}); });
}; };
export default AddPassword; // Static method to get the operation hook for automation
AddPassword.tool = () => useAddPasswordOperation;
// Static method to get default parameters for automation
AddPassword.getDefaultParameters = () => {
return defaultParameters;
};
export default AddPassword as ToolComponent;

View File

@ -21,7 +21,7 @@ import {
useWatermarkFileTips, useWatermarkFileTips,
useWatermarkFormattingTips, useWatermarkFormattingTips,
} from "../components/tooltips/useWatermarkTips"; } from "../components/tooltips/useWatermarkTips";
import { BaseToolProps } from "../types/tool"; import { BaseToolProps, ToolComponent } from "../types/tool";
const AddWatermark = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => { const AddWatermark = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -209,4 +209,7 @@ const AddWatermark = ({ onPreviewFile, onComplete, onError }: BaseToolProps) =>
}); });
}; };
export default AddWatermark; // Static method to get the operation hook for automation
AddWatermark.tool = () => useAddWatermarkOperation;
export default AddWatermark as ToolComponent;

View File

@ -76,6 +76,7 @@ const Automate = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
automation={stepData.automation} automation={stepData.automation}
onBack={() => handleStepChange({ step: 'selection'})} onBack={() => handleStepChange({ step: 'selection'})}
onComplete={handleComplete} onComplete={handleComplete}
automateOperation={automateOperation}
/> />
); );
@ -132,7 +133,7 @@ const Automate = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
}, },
steps: automationSteps, steps: automationSteps,
review: { review: {
isVisible: hasResults, isVisible: true,
operation: automateOperation, operation: automateOperation,
title: t('automate.reviewTitle', 'Automation Results') title: t('automate.reviewTitle', 'Automation Results')
} }

View File

@ -11,7 +11,7 @@ import ChangePermissionsSettings from "../components/tools/changePermissions/Cha
import { useChangePermissionsParameters } from "../hooks/tools/changePermissions/useChangePermissionsParameters"; import { useChangePermissionsParameters } from "../hooks/tools/changePermissions/useChangePermissionsParameters";
import { useChangePermissionsOperation } from "../hooks/tools/changePermissions/useChangePermissionsOperation"; import { useChangePermissionsOperation } from "../hooks/tools/changePermissions/useChangePermissionsOperation";
import { useChangePermissionsTips } from "../components/tooltips/useChangePermissionsTips"; import { useChangePermissionsTips } from "../components/tooltips/useChangePermissionsTips";
import { BaseToolProps } from "../types/tool"; import { BaseToolProps, ToolComponent } from "../types/tool";
const ChangePermissions = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => { const ChangePermissions = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -97,4 +97,7 @@ const ChangePermissions = ({ onPreviewFile, onComplete, onError }: BaseToolProps
}); });
}; };
export default ChangePermissions; // Static method to get the operation hook for automation
ChangePermissions.tool = () => useChangePermissionsOperation;
export default ChangePermissions as ToolComponent;

View File

@ -8,9 +8,9 @@ import { createToolFlow } from "../components/tools/shared/createToolFlow";
import CompressSettings from "../components/tools/compress/CompressSettings"; import CompressSettings from "../components/tools/compress/CompressSettings";
import { useCompressParameters } from "../hooks/tools/compress/useCompressParameters"; import { useCompressParameters, initialParameters } from "../hooks/tools/compress/useCompressParameters";
import { useCompressOperation } from "../hooks/tools/compress/useCompressOperation"; import { useCompressOperation } from "../hooks/tools/compress/useCompressOperation";
import { BaseToolProps } from "../types/tool"; import { BaseToolProps, ToolComponent } from "../types/tool";
import { useCompressTips } from "../components/tooltips/useCompressTips"; import { useCompressTips } from "../components/tooltips/useCompressTips";
const Compress = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => { const Compress = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
@ -95,4 +95,12 @@ const Compress = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
}); });
}; };
export default Compress; // Static method to get the operation hook for automation
Compress.tool = () => useCompressOperation;
// Static method to get default parameters for automation
Compress.getDefaultParameters = () => {
return initialParameters;
};
export default Compress as ToolComponent;

View File

@ -10,7 +10,7 @@ import ConvertSettings from "../components/tools/convert/ConvertSettings";
import { useConvertParameters } from "../hooks/tools/convert/useConvertParameters"; import { useConvertParameters } from "../hooks/tools/convert/useConvertParameters";
import { useConvertOperation } from "../hooks/tools/convert/useConvertOperation"; import { useConvertOperation } from "../hooks/tools/convert/useConvertOperation";
import { BaseToolProps } from "../types/tool"; import { BaseToolProps, ToolComponent } from "../types/tool";
const Convert = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => { const Convert = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -133,4 +133,7 @@ const Convert = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
}); });
}; };
export default Convert; // Static method to get the operation hook for automation
Convert.tool = () => useConvertOperation;
export default Convert as ToolComponent;

View File

@ -11,7 +11,7 @@ import AdvancedOCRSettings from "../components/tools/ocr/AdvancedOCRSettings";
import { useOCRParameters } from "../hooks/tools/ocr/useOCRParameters"; import { useOCRParameters } from "../hooks/tools/ocr/useOCRParameters";
import { useOCROperation } from "../hooks/tools/ocr/useOCROperation"; import { useOCROperation } from "../hooks/tools/ocr/useOCROperation";
import { BaseToolProps } from "../types/tool"; import { BaseToolProps, ToolComponent } from "../types/tool";
import { useOCRTips } from "../components/tooltips/useOCRTips"; import { useOCRTips } from "../components/tooltips/useOCRTips";
const OCR = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => { const OCR = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
@ -136,4 +136,7 @@ const OCR = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
}); });
}; };
export default OCR; // Static method to get the operation hook for automation
OCR.tool = () => useOCROperation;
export default OCR as ToolComponent;

View File

@ -8,7 +8,7 @@ import { createToolFlow } from "../components/tools/shared/createToolFlow";
import { useRemoveCertificateSignParameters } from "../hooks/tools/removeCertificateSign/useRemoveCertificateSignParameters"; import { useRemoveCertificateSignParameters } from "../hooks/tools/removeCertificateSign/useRemoveCertificateSignParameters";
import { useRemoveCertificateSignOperation } from "../hooks/tools/removeCertificateSign/useRemoveCertificateSignOperation"; import { useRemoveCertificateSignOperation } from "../hooks/tools/removeCertificateSign/useRemoveCertificateSignOperation";
import { BaseToolProps } from "../types/tool"; import { BaseToolProps, ToolComponent } from "../types/tool";
const RemoveCertificateSign = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => { const RemoveCertificateSign = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -77,4 +77,7 @@ const RemoveCertificateSign = ({ onPreviewFile, onComplete, onError }: BaseToolP
}); });
}; };
export default RemoveCertificateSign; // Static method to get the operation hook for automation
RemoveCertificateSign.tool = () => useRemoveCertificateSignOperation;
export default RemoveCertificateSign as ToolComponent;

View File

@ -11,7 +11,7 @@ import RemovePasswordSettings from "../components/tools/removePassword/RemovePas
import { useRemovePasswordParameters } from "../hooks/tools/removePassword/useRemovePasswordParameters"; import { useRemovePasswordParameters } from "../hooks/tools/removePassword/useRemovePasswordParameters";
import { useRemovePasswordOperation } from "../hooks/tools/removePassword/useRemovePasswordOperation"; import { useRemovePasswordOperation } from "../hooks/tools/removePassword/useRemovePasswordOperation";
import { useRemovePasswordTips } from "../components/tooltips/useRemovePasswordTips"; import { useRemovePasswordTips } from "../components/tooltips/useRemovePasswordTips";
import { BaseToolProps } from "../types/tool"; import { BaseToolProps, ToolComponent } from "../types/tool";
const RemovePassword = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => { const RemovePassword = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -95,4 +95,7 @@ const RemovePassword = ({ onPreviewFile, onComplete, onError }: BaseToolProps) =
}); });
}; };
export default RemovePassword; // Static method to get the operation hook for automation
RemovePassword.tool = () => useRemovePasswordOperation;
export default RemovePassword as ToolComponent;

View File

@ -8,7 +8,7 @@ import { createToolFlow } from "../components/tools/shared/createToolFlow";
import { useRepairParameters } from "../hooks/tools/repair/useRepairParameters"; import { useRepairParameters } from "../hooks/tools/repair/useRepairParameters";
import { useRepairOperation } from "../hooks/tools/repair/useRepairOperation"; import { useRepairOperation } from "../hooks/tools/repair/useRepairOperation";
import { BaseToolProps } from "../types/tool"; import { BaseToolProps, ToolComponent } from "../types/tool";
const Repair = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => { const Repair = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -77,4 +77,7 @@ const Repair = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
}); });
}; };
export default Repair; // Static method to get the operation hook for automation
Repair.tool = () => useRepairOperation;
export default Repair as ToolComponent;

View File

@ -8,7 +8,7 @@ import SanitizeSettings from "../components/tools/sanitize/SanitizeSettings";
import { useSanitizeParameters } from "../hooks/tools/sanitize/useSanitizeParameters"; import { useSanitizeParameters } from "../hooks/tools/sanitize/useSanitizeParameters";
import { useSanitizeOperation } from "../hooks/tools/sanitize/useSanitizeOperation"; import { useSanitizeOperation } from "../hooks/tools/sanitize/useSanitizeOperation";
import { BaseToolProps } from "../types/tool"; import { BaseToolProps, ToolComponent } from "../types/tool";
import { useFileContext } from "../contexts/FileContext"; import { useFileContext } from "../contexts/FileContext";
const Sanitize = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => { const Sanitize = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
@ -93,4 +93,7 @@ const Sanitize = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
}); });
}; };
export default Sanitize; // Static method to get the operation hook for automation
Sanitize.tool = () => useSanitizeOperation;
export default Sanitize as ToolComponent;

View File

@ -8,7 +8,7 @@ import { createToolFlow } from "../components/tools/shared/createToolFlow";
import { useSingleLargePageParameters } from "../hooks/tools/singleLargePage/useSingleLargePageParameters"; import { useSingleLargePageParameters } from "../hooks/tools/singleLargePage/useSingleLargePageParameters";
import { useSingleLargePageOperation } from "../hooks/tools/singleLargePage/useSingleLargePageOperation"; import { useSingleLargePageOperation } from "../hooks/tools/singleLargePage/useSingleLargePageOperation";
import { BaseToolProps } from "../types/tool"; import { BaseToolProps, ToolComponent } from "../types/tool";
const SingleLargePage = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => { const SingleLargePage = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -77,4 +77,7 @@ const SingleLargePage = ({ onPreviewFile, onComplete, onError }: BaseToolProps)
}); });
}; };
export default SingleLargePage; // Static method to get the operation hook for automation
SingleLargePage.tool = () => useSingleLargePageOperation;
export default SingleLargePage as ToolComponent;

View File

@ -9,7 +9,7 @@ import SplitSettings from "../components/tools/split/SplitSettings";
import { useSplitParameters } from "../hooks/tools/split/useSplitParameters"; import { useSplitParameters } from "../hooks/tools/split/useSplitParameters";
import { useSplitOperation } from "../hooks/tools/split/useSplitOperation"; import { useSplitOperation } from "../hooks/tools/split/useSplitOperation";
import { BaseToolProps } from "../types/tool"; import { BaseToolProps, ToolComponent } from "../types/tool";
const Split = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => { const Split = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -92,4 +92,7 @@ const Split = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
}); });
}; };
export default Split; // Static method to get the operation hook for automation
Split.tool = () => useSplitOperation;
export default Split as ToolComponent;

View File

@ -8,7 +8,7 @@ import { createToolFlow } from "../components/tools/shared/createToolFlow";
import { useUnlockPdfFormsParameters } from "../hooks/tools/unlockPdfForms/useUnlockPdfFormsParameters"; import { useUnlockPdfFormsParameters } from "../hooks/tools/unlockPdfForms/useUnlockPdfFormsParameters";
import { useUnlockPdfFormsOperation } from "../hooks/tools/unlockPdfForms/useUnlockPdfFormsOperation"; import { useUnlockPdfFormsOperation } from "../hooks/tools/unlockPdfForms/useUnlockPdfFormsOperation";
import { BaseToolProps } from "../types/tool"; import { BaseToolProps, ToolComponent } from "../types/tool";
const UnlockPdfForms = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => { const UnlockPdfForms = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -77,4 +77,7 @@ const UnlockPdfForms = ({ onPreviewFile, onComplete, onError }: BaseToolProps) =
}); });
}; };
export default UnlockPdfForms; // Static method to get the operation hook for automation
UnlockPdfForms.tool = () => useUnlockPdfFormsOperation;
export default UnlockPdfForms as ToolComponent;

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { ToolOperationHook } from '../hooks/tools/shared/useToolOperation';
export type MaxFiles = number; // 1=single, >1=limited, -1=unlimited export type MaxFiles = number; // 1=single, >1=limited, -1=unlimited
export type ToolCategory = 'manipulation' | 'conversion' | 'analysis' | 'utility' | 'optimization' | 'security'; export type ToolCategory = 'manipulation' | 'conversion' | 'analysis' | 'utility' | 'optimization' | 'security';
@ -11,6 +12,29 @@ export interface BaseToolProps {
onPreviewFile?: (file: File | null) => void; onPreviewFile?: (file: File | null) => void;
} }
/**
* Interface for tool components that support automation.
* Tools implementing this interface can be used in automation workflows.
*/
export interface AutomationCapableTool {
/**
* Static method that returns the operation hook for this tool.
* This enables automation to execute the tool programmatically.
*/
tool: () => () => ToolOperationHook<any>;
/**
* Static method that returns the default parameters for this tool.
* This enables automation creation to initialize tools with proper defaults.
*/
getDefaultParameters: () => any;
}
/**
* Type for tool components that can be used in automation
*/
export type ToolComponent = React.ComponentType<BaseToolProps> & AutomationCapableTool;
export interface ToolStepConfig { export interface ToolStepConfig {
type: ToolStepType; type: ToolStepType;
title: string; title: string;

View File

@ -0,0 +1,208 @@
import axios from 'axios';
// Tool operation configurations extracted from the hook implementations
const TOOL_CONFIGS: Record<string, any> = {
'compressPdfs': {
endpoint: '/api/v1/misc/compress-pdf',
multiFileEndpoint: false,
buildFormData: (parameters: any, file: File): FormData => {
const formData = new FormData();
formData.append("fileInput", file);
if (parameters.compressionMethod === 'quality') {
formData.append("optimizeLevel", parameters.compressionLevel?.toString() || '1');
} else {
const fileSize = parameters.fileSizeValue ? `${parameters.fileSizeValue}${parameters.fileSizeUnit}` : '';
if (fileSize) {
formData.append("expectedOutputSize", fileSize);
}
}
formData.append("grayscale", parameters.grayscale?.toString() || 'false');
return formData;
}
},
'split': {
endpoint: (parameters: any): string => {
// Simplified endpoint selection - you'd need the full logic from useSplitOperation
return "/api/v1/general/split-pages";
},
multiFileEndpoint: true,
buildFormData: (parameters: any, files: File[]): FormData => {
const formData = new FormData();
files.forEach(file => {
formData.append("fileInput", file);
});
// Add split parameters - simplified version
if (parameters.pages) {
formData.append("pageNumbers", parameters.pages);
}
return formData;
}
},
'addPassword': {
endpoint: '/api/v1/security/add-password',
multiFileEndpoint: false,
buildFormData: (parameters: any, file: File): FormData => {
const formData = new FormData();
formData.append("fileInput", file);
if (parameters.password) {
formData.append("password", parameters.password);
}
// Add other password parameters as needed
return formData;
}
}
// TODO: Add configurations for other tools
};
/**
* Extract zip files from response blob
*/
const extractZipFiles = async (blob: Blob): Promise<File[]> => {
// This would need the actual zip extraction logic from the codebase
// For now, create a single file from the blob
const file = new File([blob], `result_${Date.now()}.pdf`, { type: 'application/pdf' });
return [file];
};
/**
* Execute a tool operation directly without using React hooks
*/
export const executeToolOperation = async (
operationName: string,
parameters: any,
files: File[]
): Promise<File[]> => {
console.log(`🔧 Executing tool: ${operationName}`, { parameters, fileCount: files.length });
const config = TOOL_CONFIGS[operationName];
if (!config) {
console.error(`❌ Tool operation not supported: ${operationName}`);
throw new Error(`Tool operation not supported: ${operationName}`);
}
console.log(`📋 Using config:`, config);
try {
if (config.multiFileEndpoint) {
// Multi-file processing - single API call with all files
const endpoint = typeof config.endpoint === 'function'
? config.endpoint(parameters)
: config.endpoint;
console.log(`🌐 Making multi-file request to: ${endpoint}`);
const formData = config.buildFormData(parameters, files);
console.log(`📤 FormData entries:`, Array.from(formData.entries()));
const response = await axios.post(endpoint, formData, {
responseType: 'blob',
timeout: 300000 // 5 minute timeout for large files
});
console.log(`📥 Response status: ${response.status}, size: ${response.data.size} bytes`);
// Multi-file responses are typically ZIP files
const resultFiles = await extractZipFiles(response.data);
console.log(`📁 Extracted ${resultFiles.length} files from response`);
return resultFiles;
} else {
// Single-file processing - separate API call per file
console.log(`🔄 Processing ${files.length} files individually`);
const resultFiles: File[] = [];
for (let i = 0; i < files.length; i++) {
const file = files[i];
const endpoint = typeof config.endpoint === 'function'
? config.endpoint(parameters)
: config.endpoint;
console.log(`🌐 Making single-file request ${i+1}/${files.length} to: ${endpoint} for file: ${file.name}`);
const formData = config.buildFormData(parameters, file);
console.log(`📤 FormData entries:`, Array.from(formData.entries()));
const response = await axios.post(endpoint, formData, {
responseType: 'blob',
timeout: 300000 // 5 minute timeout for large files
});
console.log(`📥 Response ${i+1} status: ${response.status}, size: ${response.data.size} bytes`);
// Create result file
const resultFile = new File(
[response.data],
`processed_${file.name}`,
{ type: 'application/pdf' }
);
resultFiles.push(resultFile);
console.log(`✅ Created result file: ${resultFile.name}`);
}
console.log(`🎉 Single-file processing complete: ${resultFiles.length} files`);
return resultFiles;
}
} catch (error: any) {
console.error(`Tool operation ${operationName} failed:`, error);
throw new Error(`${operationName} operation failed: ${error.response?.data || error.message}`);
}
};
/**
* Execute an entire automation sequence
*/
export const executeAutomationSequence = async (
automation: any,
initialFiles: File[],
onStepStart?: (stepIndex: number, operationName: string) => void,
onStepComplete?: (stepIndex: number, resultFiles: File[]) => void,
onStepError?: (stepIndex: number, error: string) => void
): Promise<File[]> => {
console.log(`🚀 Starting automation sequence: ${automation.name || 'Unnamed'}`);
console.log(`📁 Initial files: ${initialFiles.length}`);
console.log(`🔧 Operations: ${automation.operations?.length || 0}`);
if (!automation?.operations || automation.operations.length === 0) {
throw new Error('No operations in automation');
}
let currentFiles = [...initialFiles];
for (let i = 0; i < automation.operations.length; i++) {
const operation = automation.operations[i];
console.log(`📋 Step ${i + 1}/${automation.operations.length}: ${operation.operation}`);
console.log(`📄 Input files: ${currentFiles.length}`);
console.log(`⚙️ Parameters:`, operation.parameters || {});
try {
onStepStart?.(i, operation.operation);
const resultFiles = await executeToolOperation(
operation.operation,
operation.parameters || {},
currentFiles
);
console.log(`✅ Step ${i + 1} completed: ${resultFiles.length} result files`);
currentFiles = resultFiles;
onStepComplete?.(i, resultFiles);
} catch (error: any) {
console.error(`❌ Step ${i + 1} failed:`, error);
onStepError?.(i, error.message);
throw error;
}
}
console.log(`🎉 Automation sequence completed: ${currentFiles.length} final files`);
return currentFiles;
};