From 8423bda5fce1bfc55e7cb66bb0eeffabc4c02347 Mon Sep 17 00:00:00 2001 From: James Brunton Date: Mon, 18 Aug 2025 15:45:05 +0100 Subject: [PATCH] Redesign ToolOperationConfig typing --- .../useAddPasswordOperation.test.ts | 8 +- .../addPassword/useAddPasswordOperation.ts | 4 +- .../addWatermark/useAddWatermarkOperation.ts | 6 +- .../tools/automate/useAutomateOperation.ts | 7 +- .../useChangePermissionsOperation.test.ts | 8 +- .../useChangePermissionsOperation.ts | 6 +- .../tools/compress/useCompressOperation.ts | 4 +- .../tools/convert/useConvertOperation.ts | 7 +- .../src/hooks/tools/ocr/useOCROperation.ts | 6 +- .../useRemoveCertificateSignOperation.ts | 6 +- .../useRemovePasswordOperation.test.ts | 6 +- .../useRemovePasswordOperation.ts | 4 +- .../hooks/tools/repair/useRepairOperation.ts | 6 +- .../tools/sanitize/useSanitizeOperation.ts | 3 +- .../hooks/tools/shared/useToolOperation.ts | 127 ++++++++++-------- .../useSingleLargePageOperation.ts | 6 +- .../hooks/tools/split/useSplitOperation.ts | 4 +- .../useUnlockPdfFormsOperation.ts | 6 +- frontend/src/utils/automationExecutor.ts | 56 ++++---- 19 files changed, 149 insertions(+), 131 deletions(-) diff --git a/frontend/src/hooks/tools/addPassword/useAddPasswordOperation.test.ts b/frontend/src/hooks/tools/addPassword/useAddPasswordOperation.test.ts index 7e6032214..91e36a854 100644 --- a/frontend/src/hooks/tools/addPassword/useAddPasswordOperation.test.ts +++ b/frontend/src/hooks/tools/addPassword/useAddPasswordOperation.test.ts @@ -20,13 +20,13 @@ vi.mock('../../../utils/toolErrorHandler', () => ({ })); // Import the mocked function -import { ToolOperationConfig, ToolOperationHook, useToolOperation } from '../shared/useToolOperation'; +import { SingleFileToolOperationConfig, ToolOperationHook, useToolOperation } from '../shared/useToolOperation'; describe('useAddPasswordOperation', () => { const mockUseToolOperation = vi.mocked(useToolOperation); - const getToolConfig = (): ToolOperationConfig => mockUseToolOperation.mock.calls[0][0] as ToolOperationConfig; + const getToolConfig = () => mockUseToolOperation.mock.calls[0][0] as SingleFileToolOperationConfig; const mockToolOperationReturn: ToolOperationHook = { files: [], @@ -91,7 +91,7 @@ describe('useAddPasswordOperation', () => { }; const testFile = new File(['test content'], 'test.pdf', { type: 'application/pdf' }); - const formData = buildFormData(testParameters, testFile as any /* FIX ME */); + const formData = buildFormData(testParameters, testFile); // Verify the form data contains the file expect(formData.get('fileInput')).toBe(testFile); @@ -112,7 +112,7 @@ describe('useAddPasswordOperation', () => { }); test.each([ - { property: 'multiFileEndpoint' as const, expectedValue: false }, + { property: 'toolType' as const, expectedValue: 'singleFile' }, { property: 'endpoint' as const, expectedValue: '/api/v1/security/add-password' }, { property: 'filePrefix' as const, expectedValue: 'translated-addPassword.filenamePrefix_' }, { property: 'operationType' as const, expectedValue: 'addPassword' } diff --git a/frontend/src/hooks/tools/addPassword/useAddPasswordOperation.ts b/frontend/src/hooks/tools/addPassword/useAddPasswordOperation.ts index bd2f2176c..f80c94c19 100644 --- a/frontend/src/hooks/tools/addPassword/useAddPasswordOperation.ts +++ b/frontend/src/hooks/tools/addPassword/useAddPasswordOperation.ts @@ -26,11 +26,11 @@ const fullDefaultParameters: AddPasswordFullParameters = { // Static configuration object export const addPasswordOperationConfig = { + toolType: 'singleFile', + buildFormData: buildAddPasswordFormData, operationType: 'addPassword', endpoint: '/api/v1/security/add-password', - buildFormData: buildAddPasswordFormData, filePrefix: 'encrypted_', // Will be overridden in hook with translation - multiFileEndpoint: false, defaultParameters: fullDefaultParameters, } as const; diff --git a/frontend/src/hooks/tools/addWatermark/useAddWatermarkOperation.ts b/frontend/src/hooks/tools/addWatermark/useAddWatermarkOperation.ts index 5c07ee6e7..a852c7807 100644 --- a/frontend/src/hooks/tools/addWatermark/useAddWatermarkOperation.ts +++ b/frontend/src/hooks/tools/addWatermark/useAddWatermarkOperation.ts @@ -35,11 +35,11 @@ export const buildAddWatermarkFormData = (parameters: AddWatermarkParameters, fi // Static configuration object export const addWatermarkOperationConfig = { + toolType: 'singleFile', + buildFormData: buildAddWatermarkFormData, operationType: 'watermark', endpoint: '/api/v1/security/add-watermark', - buildFormData: buildAddWatermarkFormData, filePrefix: 'watermarked_', // Will be overridden in hook with translation - multiFileEndpoint: false, defaultParameters, } as const; @@ -51,4 +51,4 @@ export const useAddWatermarkOperation = () => { filePrefix: t('watermark.filenamePrefix', 'watermarked') + '_', getErrorMessage: createStandardErrorHandler(t('watermark.error.failed', 'An error occurred while adding watermark to the PDF.')) }); -}; \ No newline at end of file +}; diff --git a/frontend/src/hooks/tools/automate/useAutomateOperation.ts b/frontend/src/hooks/tools/automate/useAutomateOperation.ts index f1aeaf900..6573deffb 100644 --- a/frontend/src/hooks/tools/automate/useAutomateOperation.ts +++ b/frontend/src/hooks/tools/automate/useAutomateOperation.ts @@ -10,7 +10,7 @@ export function useAutomateOperation() { const customProcessor = useCallback(async (params: AutomateParameters, files: File[]) => { console.log('🚀 Starting automation execution via customProcessor', { params, files }); - + if (!params.automationConfig) { throw new Error('No automation configuration provided'); } @@ -40,10 +40,9 @@ export function useAutomateOperation() { }, [toolRegistry]); return useToolOperation({ + toolType: 'custom', operationType: 'automate', - endpoint: '/api/v1/pipeline/handleData', // Not used with customProcessor - buildFormData: () => new FormData(), // Not used with customProcessor customProcessor, filePrefix: AUTOMATION_CONSTANTS.FILE_PREFIX }); -} \ No newline at end of file +} diff --git a/frontend/src/hooks/tools/changePermissions/useChangePermissionsOperation.test.ts b/frontend/src/hooks/tools/changePermissions/useChangePermissionsOperation.test.ts index 4ffd9b45c..41c257d71 100644 --- a/frontend/src/hooks/tools/changePermissions/useChangePermissionsOperation.test.ts +++ b/frontend/src/hooks/tools/changePermissions/useChangePermissionsOperation.test.ts @@ -20,12 +20,12 @@ vi.mock('../../../utils/toolErrorHandler', () => ({ })); // Import the mocked function -import { ToolOperationConfig, ToolOperationHook, useToolOperation } from '../shared/useToolOperation'; +import { SingleFileToolOperationConfig, ToolOperationHook, useToolOperation } from '../shared/useToolOperation'; describe('useChangePermissionsOperation', () => { const mockUseToolOperation = vi.mocked(useToolOperation); - const getToolConfig = (): ToolOperationConfig => mockUseToolOperation.mock.calls[0][0] as ToolOperationConfig; + const getToolConfig = () => mockUseToolOperation.mock.calls[0][0] as SingleFileToolOperationConfig; const mockToolOperationReturn: ToolOperationHook = { files: [], @@ -86,7 +86,7 @@ describe('useChangePermissionsOperation', () => { const buildFormData = callArgs.buildFormData; const testFile = new File(['test content'], 'test.pdf', { type: 'application/pdf' }); - const formData = buildFormData(testParameters, testFile as any /* FIX ME */); + const formData = buildFormData(testParameters, testFile); // Verify the form data contains the file expect(formData.get('fileInput')).toBe(testFile); @@ -106,7 +106,7 @@ describe('useChangePermissionsOperation', () => { }); test.each([ - { property: 'multiFileEndpoint' as const, expectedValue: false }, + { property: 'toolType' as const, expectedValue: 'singleFile' }, { property: 'endpoint' as const, expectedValue: '/api/v1/security/add-password' }, { property: 'filePrefix' as const, expectedValue: 'permissions_' }, { property: 'operationType' as const, expectedValue: 'change-permissions' } diff --git a/frontend/src/hooks/tools/changePermissions/useChangePermissionsOperation.ts b/frontend/src/hooks/tools/changePermissions/useChangePermissionsOperation.ts index ee28d5381..6d976a326 100644 --- a/frontend/src/hooks/tools/changePermissions/useChangePermissionsOperation.ts +++ b/frontend/src/hooks/tools/changePermissions/useChangePermissionsOperation.ts @@ -24,11 +24,11 @@ export const buildChangePermissionsFormData = (parameters: ChangePermissionsPara // Static configuration object export const changePermissionsOperationConfig = { + toolType: 'singleFile', + buildFormData: buildChangePermissionsFormData, operationType: 'change-permissions', endpoint: '/api/v1/security/add-password', // Change Permissions is a fake endpoint for the Add Password tool - buildFormData: buildChangePermissionsFormData, filePrefix: 'permissions_', - multiFileEndpoint: false, defaultParameters, } as const; @@ -39,6 +39,6 @@ export const useChangePermissionsOperation = () => { ...changePermissionsOperationConfig, getErrorMessage: createStandardErrorHandler( t('changePermissions.error.failed', 'An error occurred while changing PDF permissions.') - ) + ), }); }; diff --git a/frontend/src/hooks/tools/compress/useCompressOperation.ts b/frontend/src/hooks/tools/compress/useCompressOperation.ts index 08d73859e..85ee918cb 100644 --- a/frontend/src/hooks/tools/compress/useCompressOperation.ts +++ b/frontend/src/hooks/tools/compress/useCompressOperation.ts @@ -24,11 +24,11 @@ export const buildCompressFormData = (parameters: CompressParameters, file: File // Static configuration object export const compressOperationConfig = { + toolType: 'singleFile', + buildFormData: buildCompressFormData, operationType: 'compress', endpoint: '/api/v1/misc/compress-pdf', - buildFormData: buildCompressFormData, filePrefix: 'compressed_', - multiFileEndpoint: false, // Individual API calls per file defaultParameters, } as const; diff --git a/frontend/src/hooks/tools/convert/useConvertOperation.ts b/frontend/src/hooks/tools/convert/useConvertOperation.ts index 28b854d4b..3457c8426 100644 --- a/frontend/src/hooks/tools/convert/useConvertOperation.ts +++ b/frontend/src/hooks/tools/convert/useConvertOperation.ts @@ -129,11 +129,10 @@ export const convertProcessor = async ( // Static configuration object export const convertOperationConfig = { + toolType: 'custom', + customProcessor: convertProcessor, // Can't use callback version here operationType: 'convert', - endpoint: '', // Not used with customProcessor but required - buildFormData: buildConvertFormData, // Not used with customProcessor but required filePrefix: 'converted_', - customProcessor: convertProcessor, defaultParameters, } as const; @@ -158,6 +157,6 @@ export const useConvertOperation = () => { return error.message; } return t("convert.errorConversion", "An error occurred while converting the file."); - } + }, }); }; diff --git a/frontend/src/hooks/tools/ocr/useOCROperation.ts b/frontend/src/hooks/tools/ocr/useOCROperation.ts index b816b3773..311c336fd 100644 --- a/frontend/src/hooks/tools/ocr/useOCROperation.ts +++ b/frontend/src/hooks/tools/ocr/useOCROperation.ts @@ -52,7 +52,7 @@ export const buildOCRFormData = (parameters: OCRParameters, file: File): FormDat return formData; }; -// Static response handler for OCR - can be used by automation executor +// Static response handler for OCR - can be used by automation executor export const ocrResponseHandler = async (blob: Blob, originalFiles: File[], extractZipFiles: (blob: Blob) => Promise): Promise => { const headBuf = await blob.slice(0, 8).arrayBuffer(); const head = new TextDecoder().decode(new Uint8Array(headBuf)); @@ -94,11 +94,11 @@ export const ocrResponseHandler = async (blob: Blob, originalFiles: File[], extr // Static configuration object (without t function dependencies) export const ocrOperationConfig = { + toolType: 'singleFile', + buildFormData: buildOCRFormData, operationType: 'ocr', endpoint: '/api/v1/misc/ocr-pdf', - buildFormData: buildOCRFormData, filePrefix: 'ocr_', - multiFileEndpoint: false, defaultParameters, } as const; diff --git a/frontend/src/hooks/tools/removeCertificateSign/useRemoveCertificateSignOperation.ts b/frontend/src/hooks/tools/removeCertificateSign/useRemoveCertificateSignOperation.ts index c466606bc..853e40f11 100644 --- a/frontend/src/hooks/tools/removeCertificateSign/useRemoveCertificateSignOperation.ts +++ b/frontend/src/hooks/tools/removeCertificateSign/useRemoveCertificateSignOperation.ts @@ -12,11 +12,11 @@ export const buildRemoveCertificateSignFormData = (parameters: RemoveCertificate // Static configuration object export const removeCertificateSignOperationConfig = { + toolType: 'singleFile', + buildFormData: buildRemoveCertificateSignFormData, operationType: 'remove-certificate-sign', endpoint: '/api/v1/security/remove-cert-sign', - buildFormData: buildRemoveCertificateSignFormData, filePrefix: 'unsigned_', // Will be overridden in hook with translation - multiFileEndpoint: false, defaultParameters, } as const; @@ -28,4 +28,4 @@ export const useRemoveCertificateSignOperation = () => { filePrefix: t('removeCertSign.filenamePrefix', 'unsigned') + '_', getErrorMessage: createStandardErrorHandler(t('removeCertSign.error.failed', 'An error occurred while removing certificate signatures.')) }); -}; \ No newline at end of file +}; diff --git a/frontend/src/hooks/tools/removePassword/useRemovePasswordOperation.test.ts b/frontend/src/hooks/tools/removePassword/useRemovePasswordOperation.test.ts index 61faef9a4..4b83d74a8 100644 --- a/frontend/src/hooks/tools/removePassword/useRemovePasswordOperation.test.ts +++ b/frontend/src/hooks/tools/removePassword/useRemovePasswordOperation.test.ts @@ -20,12 +20,12 @@ vi.mock('../../../utils/toolErrorHandler', () => ({ })); // Import the mocked function -import { ToolOperationConfig, ToolOperationHook, useToolOperation } from '../shared/useToolOperation'; +import { SingleFileToolOperationConfig, ToolOperationHook, useToolOperation } from '../shared/useToolOperation'; describe('useRemovePasswordOperation', () => { const mockUseToolOperation = vi.mocked(useToolOperation); - const getToolConfig = (): ToolOperationConfig => mockUseToolOperation.mock.calls[0][0] as ToolOperationConfig; + const getToolConfig = () => mockUseToolOperation.mock.calls[0][0] as SingleFileToolOperationConfig; const mockToolOperationReturn: ToolOperationHook = { files: [], @@ -91,7 +91,7 @@ describe('useRemovePasswordOperation', () => { }); test.each([ - { property: 'multiFileEndpoint' as const, expectedValue: false }, + { property: 'toolType' as const, expectedValue: 'singleFile' }, { property: 'endpoint' as const, expectedValue: '/api/v1/security/remove-password' }, { property: 'filePrefix' as const, expectedValue: 'translated-removePassword.filenamePrefix_' }, { property: 'operationType' as const, expectedValue: 'removePassword' } diff --git a/frontend/src/hooks/tools/removePassword/useRemovePasswordOperation.ts b/frontend/src/hooks/tools/removePassword/useRemovePasswordOperation.ts index 9d61d3c59..96b97a212 100644 --- a/frontend/src/hooks/tools/removePassword/useRemovePasswordOperation.ts +++ b/frontend/src/hooks/tools/removePassword/useRemovePasswordOperation.ts @@ -13,11 +13,11 @@ export const buildRemovePasswordFormData = (parameters: RemovePasswordParameters // Static configuration object export const removePasswordOperationConfig = { + toolType: 'singleFile', + buildFormData: buildRemovePasswordFormData, operationType: 'removePassword', endpoint: '/api/v1/security/remove-password', - buildFormData: buildRemovePasswordFormData, filePrefix: 'decrypted_', // Will be overridden in hook with translation - multiFileEndpoint: false, defaultParameters, } as const; diff --git a/frontend/src/hooks/tools/repair/useRepairOperation.ts b/frontend/src/hooks/tools/repair/useRepairOperation.ts index 8be4fb13f..4929f4717 100644 --- a/frontend/src/hooks/tools/repair/useRepairOperation.ts +++ b/frontend/src/hooks/tools/repair/useRepairOperation.ts @@ -12,11 +12,11 @@ export const buildRepairFormData = (parameters: RepairParameters, file: File): F // Static configuration object export const repairOperationConfig = { + toolType: 'singleFile', + buildFormData: buildRepairFormData, operationType: 'repair', endpoint: '/api/v1/misc/repair', - buildFormData: buildRepairFormData, filePrefix: 'repaired_', // Will be overridden in hook with translation - multiFileEndpoint: false, defaultParameters, } as const; @@ -28,4 +28,4 @@ export const useRepairOperation = () => { filePrefix: t('repair.filenamePrefix', 'repaired') + '_', getErrorMessage: createStandardErrorHandler(t('repair.error.failed', 'An error occurred while repairing the PDF.')) }); -}; \ No newline at end of file +}; diff --git a/frontend/src/hooks/tools/sanitize/useSanitizeOperation.ts b/frontend/src/hooks/tools/sanitize/useSanitizeOperation.ts index 284b715f8..8038f7bdb 100644 --- a/frontend/src/hooks/tools/sanitize/useSanitizeOperation.ts +++ b/frontend/src/hooks/tools/sanitize/useSanitizeOperation.ts @@ -21,9 +21,10 @@ export const buildSanitizeFormData = (parameters: SanitizeParameters, file: File // Static configuration object export const sanitizeOperationConfig = { + toolType: 'singleFile', + buildFormData: buildSanitizeFormData, operationType: 'sanitize', endpoint: '/api/v1/security/sanitize-pdf', - buildFormData: buildSanitizeFormData, filePrefix: 'sanitized_', // Will be overridden in hook with translation multiFileEndpoint: false, defaultParameters, diff --git a/frontend/src/hooks/tools/shared/useToolOperation.ts b/frontend/src/hooks/tools/shared/useToolOperation.ts index 299568afd..29f1c1d34 100644 --- a/frontend/src/hooks/tools/shared/useToolOperation.ts +++ b/frontend/src/hooks/tools/shared/useToolOperation.ts @@ -12,6 +12,8 @@ import { ResponseHandler } from '../../../utils/toolResponseProcessor'; // Re-export for backwards compatibility export type { ProcessingProgress, ResponseHandler }; +export type ToolConfigType = 'singleFile' | 'multiFile' | 'custom'; + /** * Configuration for tool operations defining processing behavior and API integration. * @@ -20,45 +22,16 @@ export type { ProcessingProgress, ResponseHandler }; * 2. Multi-file tools: multiFileEndpoint: true, single API call with all files * 3. Complex tools: customProcessor handles all processing logic */ -export interface ToolOperationConfig { +interface BaseToolOperationConfig { /** Operation identifier for tracking and logging */ operationType: string; - /** - * API endpoint for the operation. Can be static string or function for dynamic routing. - * Not used when customProcessor is provided. - */ - endpoint: string | ((params: TParams) => string); - - /** - * Builds FormData for API request. Signature determines processing approach: - * - (params, file: File) => FormData: Single-file processing - * - (params, files: File[]) => FormData: Multi-file processing - * Not used when customProcessor is provided. - */ - buildFormData: ((params: TParams, file: File) => FormData) | ((params: TParams, files: File[]) => FormData); /* FIX ME */ - /** Prefix added to processed filenames (e.g., 'compressed_', 'split_') */ filePrefix: string; - /** - * Whether this tool uses backends that accept MultipartFile[] arrays. - * - true: Single API call with all files (backend uses MultipartFile[]) - * - false/undefined: Individual API calls per file (backend uses single MultipartFile) - * Ignored when customProcessor is provided. - */ - multiFileEndpoint?: boolean; - /** How to handle API responses (e.g., ZIP extraction, single file response) */ responseHandler?: ResponseHandler; - /** - * Custom processing logic that completely bypasses standard file processing. - * When provided, tool handles all API calls, response processing, and file creation. - * Use for tools with complex routing logic or non-standard processing requirements. - */ - customProcessor?: (params: TParams, files: File[]) => Promise; - /** Extract user-friendly error messages from API errors */ getErrorMessage?: (error: any) => string; @@ -66,6 +39,49 @@ export interface ToolOperationConfig { defaultParameters?: TParams; } +export interface SingleFileToolOperationConfig extends BaseToolOperationConfig { + /** This tool processes one file at a time. */ + toolType: 'singleFile'; + + /** Builds FormData for API request. */ + buildFormData: ((params: TParams, file: File) => FormData); + + /** API endpoint for the operation. Can be static string or function for dynamic routing. */ + endpoint: string | ((params: TParams) => string); + + customProcessor?: undefined; +} + +export interface MultiFileToolOperationConfig extends BaseToolOperationConfig { + /** This tool processes multiple files at once. */ + toolType: 'multiFile'; + + /** Builds FormData for API request. */ + buildFormData: ((params: TParams, files: File[]) => FormData); + + /** API endpoint for the operation. Can be static string or function for dynamic routing. */ + endpoint: string | ((params: TParams) => string); + + customProcessor?: undefined; +} + +export interface CustomToolOperationConfig extends BaseToolOperationConfig { + /** This tool has custom behaviour. */ + toolType: 'custom'; + + buildFormData?: undefined; + endpoint?: undefined; + + /** + * Custom processing logic that completely bypasses standard file processing. + * This tool handles all API calls, response processing, and file creation. + * Use for tools with complex routing logic or non-standard processing requirements. + */ + customProcessor: (params: TParams, files: File[]) => Promise; +} + +export type ToolOperationConfig = SingleFileToolOperationConfig | MultiFileToolOperationConfig | CustomToolOperationConfig; + /** * Complete tool operation interface with execution capability */ @@ -103,7 +119,7 @@ export { createStandardErrorHandler } from '../../../utils/toolErrorHandler'; * @param config - Tool operation configuration * @returns Hook interface with state and execution methods */ -export const useToolOperation = ( +export const useToolOperation = ( config: ToolOperationConfig ): ToolOperationHook => { const { t } = useTranslation(); @@ -143,15 +159,28 @@ export const useToolOperation = ( try { let processedFiles: File[]; - if (config.customProcessor) { - actions.setStatus('Processing files...'); - processedFiles = await config.customProcessor(params, validFiles); - } else { - // Use explicit multiFileEndpoint flag to determine processing approach - if (config.multiFileEndpoint) { + switch (config.toolType) { + case 'singleFile': + // Individual file processing - separate API call per file + const apiCallsConfig: ApiCallsConfig = { + endpoint: config.endpoint, + buildFormData: config.buildFormData, + filePrefix: config.filePrefix, + responseHandler: config.responseHandler + }; + processedFiles = await processFiles( + params, + validFiles, + apiCallsConfig, + actions.setProgress, + actions.setStatus + ); + break; + + case 'multiFile': // Multi-file processing - single API call with all files actions.setStatus('Processing files...'); - const formData = (config.buildFormData as (params: TParams, files: File[]) => FormData)(params, validFiles); + const formData = config.buildFormData(params, validFiles); const endpoint = typeof config.endpoint === 'function' ? config.endpoint(params) : config.endpoint; const response = await axios.post(endpoint, formData, { responseType: 'blob' }); @@ -169,22 +198,12 @@ export const useToolOperation = ( processedFiles = await extractAllZipFiles(response.data); } } - } else { - // Individual file processing - separate API call per file - const apiCallsConfig: ApiCallsConfig = { - endpoint: config.endpoint, - buildFormData: config.buildFormData as (params: TParams, file: File) => FormData, - filePrefix: config.filePrefix, - responseHandler: config.responseHandler - }; - processedFiles = await processFiles( - params, - validFiles, - apiCallsConfig, - actions.setProgress, - actions.setStatus - ); - } + break; + + case 'custom': + actions.setStatus('Processing files...'); + processedFiles = await config.customProcessor(params, validFiles); + break; } if (processedFiles.length > 0) { diff --git a/frontend/src/hooks/tools/singleLargePage/useSingleLargePageOperation.ts b/frontend/src/hooks/tools/singleLargePage/useSingleLargePageOperation.ts index 41cbb6dd3..5f6b89b87 100644 --- a/frontend/src/hooks/tools/singleLargePage/useSingleLargePageOperation.ts +++ b/frontend/src/hooks/tools/singleLargePage/useSingleLargePageOperation.ts @@ -12,11 +12,11 @@ export const buildSingleLargePageFormData = (parameters: SingleLargePageParamete // Static configuration object export const singleLargePageOperationConfig = { + toolType: 'singleFile', + buildFormData: buildSingleLargePageFormData, operationType: 'single-large-page', endpoint: '/api/v1/general/pdf-to-single-page', - buildFormData: buildSingleLargePageFormData, filePrefix: 'single_page_', // Will be overridden in hook with translation - multiFileEndpoint: false, defaultParameters, } as const; @@ -28,4 +28,4 @@ export const useSingleLargePageOperation = () => { filePrefix: t('pdfToSinglePage.filenamePrefix', 'single_page') + '_', getErrorMessage: createStandardErrorHandler(t('pdfToSinglePage.error.failed', 'An error occurred while converting to single page.')) }); -}; \ No newline at end of file +}; diff --git a/frontend/src/hooks/tools/split/useSplitOperation.ts b/frontend/src/hooks/tools/split/useSplitOperation.ts index cc1c6a5d9..c7a576695 100644 --- a/frontend/src/hooks/tools/split/useSplitOperation.ts +++ b/frontend/src/hooks/tools/split/useSplitOperation.ts @@ -57,11 +57,11 @@ export const getSplitEndpoint = (parameters: SplitParameters): string => { // Static configuration object export const splitOperationConfig = { + toolType: 'multiFile', + buildFormData: buildSplitFormData, operationType: 'splitPdf', endpoint: getSplitEndpoint, - buildFormData: buildSplitFormData, filePrefix: 'split_', - multiFileEndpoint: true, // Single API call with all files defaultParameters, } as const; diff --git a/frontend/src/hooks/tools/unlockPdfForms/useUnlockPdfFormsOperation.ts b/frontend/src/hooks/tools/unlockPdfForms/useUnlockPdfFormsOperation.ts index d6081452f..6d88b4cba 100644 --- a/frontend/src/hooks/tools/unlockPdfForms/useUnlockPdfFormsOperation.ts +++ b/frontend/src/hooks/tools/unlockPdfForms/useUnlockPdfFormsOperation.ts @@ -12,11 +12,11 @@ export const buildUnlockPdfFormsFormData = (parameters: UnlockPdfFormsParameters // Static configuration object export const unlockPdfFormsOperationConfig = { + toolType: 'singleFile', + buildFormData: buildUnlockPdfFormsFormData, operationType: 'unlock-pdf-forms', endpoint: '/api/v1/misc/unlock-pdf-forms', - buildFormData: buildUnlockPdfFormsFormData, filePrefix: 'unlocked_forms_', // Will be overridden in hook with translation - multiFileEndpoint: false, defaultParameters, } as const; @@ -28,4 +28,4 @@ export const useUnlockPdfFormsOperation = () => { filePrefix: t('unlockPDFForms.filenamePrefix', 'unlocked_forms') + '_', getErrorMessage: createStandardErrorHandler(t('unlockPDFForms.error.failed', 'An error occurred while unlocking PDF forms.')) }); -}; \ No newline at end of file +}; diff --git a/frontend/src/utils/automationExecutor.ts b/frontend/src/utils/automationExecutor.ts index 9ef1f6e2f..89bb6c2da 100644 --- a/frontend/src/utils/automationExecutor.ts +++ b/frontend/src/utils/automationExecutor.ts @@ -10,13 +10,13 @@ import { ResourceManager } from './resourceManager'; * Execute a tool operation directly without using React hooks */ export const executeToolOperation = async ( - operationName: string, - parameters: any, + operationName: string, + parameters: any, files: File[], toolRegistry: ToolRegistry ): Promise => { console.log(`🔧 Executing tool: ${operationName}`, { parameters, fileCount: files.length }); - + const config = toolRegistry[operationName]?.operationConfig; if (!config) { console.error(`❌ Tool operation not supported: ${operationName}`); @@ -34,17 +34,17 @@ export const executeToolOperation = async ( return resultFiles; } - if (config.multiFileEndpoint) { + if (config.toolType === 'multiFile') { // Multi-file processing - single API call with all files - const endpoint = typeof config.endpoint === 'function' - ? config.endpoint(parameters) + const endpoint = typeof config.endpoint === 'function' + ? config.endpoint(parameters) : config.endpoint; - + console.log(`🌐 Making multi-file request to: ${endpoint}`); const formData = (config.buildFormData as (params: any, files: File[]) => FormData)(parameters, files); console.log(`📤 FormData entries:`, Array.from(formData.entries())); - - const response = await axios.post(endpoint, formData, { + + const response = await axios.post(endpoint, formData, { responseType: 'blob', timeout: AUTOMATION_CONSTANTS.OPERATION_TIMEOUT }); @@ -53,11 +53,11 @@ export const executeToolOperation = async ( // Multi-file responses are typically ZIP files, but may be single files const result = await AutomationFileProcessor.extractAutomationZipFiles(response.data); - + if (result.errors.length > 0) { console.warn(`⚠️ File processing warnings:`, result.errors); } - + console.log(`📁 Processed ${result.files.length} files from response`); return result.files; @@ -65,18 +65,18 @@ export const executeToolOperation = async ( // 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) + 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 as (params: any, file: File) => FormData)(parameters, file); console.log(`📤 FormData entries:`, Array.from(formData.entries())); - - const response = await axios.post(endpoint, formData, { + + const response = await axios.post(endpoint, formData, { responseType: 'blob', timeout: AUTOMATION_CONSTANTS.OPERATION_TIMEOUT }); @@ -85,7 +85,7 @@ export const executeToolOperation = async ( // Create result file const resultFile = ResourceManager.createResultFile( - response.data, + response.data, file.name, AUTOMATION_CONSTANTS.FILE_PREFIX ); @@ -107,7 +107,7 @@ export const executeToolOperation = async ( * Execute an entire automation sequence */ export const executeAutomationSequence = async ( - automation: any, + automation: any, initialFiles: File[], toolRegistry: ToolRegistry, onStepStart?: (stepIndex: number, operationName: string) => void, @@ -117,7 +117,7 @@ export const executeAutomationSequence = async ( 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'); } @@ -126,25 +126,25 @@ export const executeAutomationSequence = async ( 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 || {}, + operation.operation, + operation.parameters || {}, currentFiles, toolRegistry ); - + 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); @@ -154,4 +154,4 @@ export const executeAutomationSequence = async ( console.log(`🎉 Automation sequence completed: ${currentFiles.length} final files`); return currentFiles; -}; \ No newline at end of file +};