mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-28 23:29:23 +00:00

# Description of Changes Redesigns `ToolOperationConfig` so that the types of the functions are always known depending on whether the tool runs on single files, multiple files, or uses custom behaviour
196 lines
7.3 KiB
TypeScript
196 lines
7.3 KiB
TypeScript
import axios from 'axios';
|
|
import { ToolRegistry } from '../data/toolsTaxonomy';
|
|
import { AutomationConfig, AutomationExecutionCallbacks } from '../types/automation';
|
|
import { AUTOMATION_CONSTANTS } from '../constants/automation';
|
|
import { AutomationFileProcessor } from './automationFileProcessor';
|
|
import { ResourceManager } from './resourceManager';
|
|
import { ToolType } from '../hooks/tools/shared/useToolOperation';
|
|
|
|
|
|
/**
|
|
* Execute a tool operation directly without using React hooks
|
|
*/
|
|
export const executeToolOperation = async (
|
|
operationName: string,
|
|
parameters: any,
|
|
files: File[],
|
|
toolRegistry: ToolRegistry
|
|
): Promise<File[]> => {
|
|
return executeToolOperationWithPrefix(operationName, parameters, files, toolRegistry, AUTOMATION_CONSTANTS.FILE_PREFIX);
|
|
};
|
|
|
|
/**
|
|
* Execute a tool operation with custom prefix
|
|
*/
|
|
export const executeToolOperationWithPrefix = async (
|
|
operationName: string,
|
|
parameters: any,
|
|
files: File[],
|
|
toolRegistry: ToolRegistry,
|
|
filePrefix: string = AUTOMATION_CONSTANTS.FILE_PREFIX
|
|
): Promise<File[]> => {
|
|
console.log(`🔧 Executing tool: ${operationName}`, { parameters, fileCount: files.length });
|
|
|
|
const config = toolRegistry[operationName]?.operationConfig;
|
|
if (!config) {
|
|
console.error(`❌ Tool operation not supported: ${operationName}`);
|
|
throw new Error(`Tool operation not supported: ${operationName}`);
|
|
}
|
|
|
|
console.log(`📋 Using config:`, config);
|
|
|
|
try {
|
|
// Check if tool uses custom processor (like Convert tool)
|
|
if (config.customProcessor) {
|
|
console.log(`🎯 Using custom processor for ${config.operationType}`);
|
|
const resultFiles = await config.customProcessor(parameters, files);
|
|
console.log(`✅ Custom processor returned ${resultFiles.length} files`);
|
|
return resultFiles;
|
|
}
|
|
|
|
if (config.toolType === ToolType.multiFile) {
|
|
// 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 as (params: any, files: File[]) => FormData)(parameters, files);
|
|
console.log(`📤 FormData entries:`, Array.from(formData.entries()));
|
|
|
|
const response = await axios.post(endpoint, formData, {
|
|
responseType: 'blob',
|
|
timeout: AUTOMATION_CONSTANTS.OPERATION_TIMEOUT
|
|
});
|
|
|
|
console.log(`📥 Response status: ${response.status}, size: ${response.data.size} bytes`);
|
|
|
|
// Multi-file responses are typically ZIP files, but may be single files (e.g. split with merge=true)
|
|
let result;
|
|
if (response.data.type === 'application/pdf' ||
|
|
(response.headers && response.headers['content-type'] === 'application/pdf')) {
|
|
// Single PDF response (e.g. split with merge option) - use original filename
|
|
const originalFileName = files[0]?.name || 'document.pdf';
|
|
const singleFile = new File([response.data], originalFileName, { type: 'application/pdf' });
|
|
result = {
|
|
success: true,
|
|
files: [singleFile],
|
|
errors: []
|
|
};
|
|
} else {
|
|
// ZIP response
|
|
result = await AutomationFileProcessor.extractAutomationZipFiles(response.data);
|
|
}
|
|
|
|
if (result.errors.length > 0) {
|
|
console.warn(`⚠️ File processing warnings:`, result.errors);
|
|
}
|
|
// Apply prefix to files, replacing any existing prefix
|
|
const processedFiles = filePrefix
|
|
? result.files.map(file => {
|
|
const nameWithoutPrefix = file.name.replace(/^[^_]*_/, '');
|
|
return new File([file], `${filePrefix}${nameWithoutPrefix}`, { type: file.type });
|
|
})
|
|
: result.files;
|
|
|
|
console.log(`📁 Processed ${processedFiles.length} files from response`);
|
|
return processedFiles;
|
|
|
|
} 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 as (params: any, file: File) => FormData)(parameters, file);
|
|
console.log(`📤 FormData entries:`, Array.from(formData.entries()));
|
|
|
|
const response = await axios.post(endpoint, formData, {
|
|
responseType: 'blob',
|
|
timeout: AUTOMATION_CONSTANTS.OPERATION_TIMEOUT
|
|
});
|
|
|
|
console.log(`📥 Response ${i+1} status: ${response.status}, size: ${response.data.size} bytes`);
|
|
|
|
// Create result file with automation prefix
|
|
|
|
const resultFile = ResourceManager.createResultFile(
|
|
response.data,
|
|
file.name,
|
|
filePrefix
|
|
);
|
|
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[],
|
|
toolRegistry: ToolRegistry,
|
|
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];
|
|
const automationPrefix = automation.name ? `${automation.name}_` : 'automated_';
|
|
|
|
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 executeToolOperationWithPrefix(
|
|
operation.operation,
|
|
operation.parameters || {},
|
|
currentFiles,
|
|
toolRegistry,
|
|
i === automation.operations.length - 1 ? automationPrefix : '' // Only add prefix to final step
|
|
);
|
|
|
|
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;
|
|
};
|