mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-03-04 02:20:19 +01:00
# Description of Changes This pull request introduces the new "Auto Rename PDF" tool to the frontend, enabling users to automatically rename PDF files based on their content. The implementation includes UI components, parameter handling, operation logic, localization, and enhancements to the file response utilities to support backend-provided filenames. Below are the most important changes grouped by theme: **Feature: Auto Rename PDF Tool** - Added the main `AutoRename` tool component (`AutoRename.tsx`) and registered it in the tool registry, enabling selection and execution of the auto-rename operation in the UI. [[1]](diffhunk://#diff-3647ca39d46d109d122d4cd6cbfe981beb4189d05b1b446e5c46824eb98a4a88R1-R80) [[2]](diffhunk://#diff-0a3e636736c137356dd9354ff3cacbd302ebda40147545e13c62d073525d1969R17) [[3]](diffhunk://#diff-0a3e636736c137356dd9354ff3cacbd302ebda40147545e13c62d073525d1969L359-R366) [[4]](diffhunk://#diff-29427b8d06a23772c56645fc4b72af2980c813605abc162e3d47c2e39d026d06L25-R26) - Implemented the settings panel (`AutoRenameSettings.tsx`) and parameter management hook (`useAutoRenameParameters.ts`), allowing users to configure options such as using the first text as a fallback for the filename. [[1]](diffhunk://#diff-b2f9474c8e5a7a42df00a12ffd2d31a785895fe1096e8ca515e6af5633a4d648R1-R27) [[2]](diffhunk://#diff-8798a1ef451233bf3a1bf8825c12c5b434ad1a17a1beb1ca21fd972fdaceb50cR1-R19) - Created the operation hook (`useAutoRenameOperation.ts`) to handle API requests, error handling, and result processing for the auto-rename feature. **Localization** - Added English (US and GB) translations for the new tool, including UI labels, descriptions, error messages, and settings. [[1]](diffhunk://#diff-e4d543afa388d9eb8a423e45dfebb91641e3558d00848d70b285ebb91c40b249R1048-R1066) [[2]](diffhunk://#diff-14c707e28788a3a84ed5293ff6689be73d4bca00e155beaf090f9b37c978babbR1321-R1339) **File Response Handling Enhancements** - Updated the file response processor and related hooks to support preserving backend-provided filenames via the `Content-Disposition` header, ensuring files are renamed according to backend results. [[1]](diffhunk://#diff-97ea1c842d4b269c566a3085d8555ded7f9b462d9ce8dc73706bec79fe3973e0R11) [[2]](diffhunk://#diff-97ea1c842d4b269c566a3085d8555ded7f9b462d9ce8dc73706bec79fe3973e0L49-R51) [[3]](diffhunk://#diff-d44da7f96721d9829f3c20bf9c7ac5b9e156b647d2c75d76e861c8c09abc5191R52-R58) [[4]](diffhunk://#diff-d44da7f96721d9829f3c20bf9c7ac5b9e156b647d2c75d76e861c8c09abc5191L175-R183) [[5]](diffhunk://#diff-fa8af80f4d87370d58e3a5b79df675d201f0c3aa753eda89cec03ff027c4213dL13-R21) [[6]](diffhunk://#diff-efa525dbdeceaeb5701aa3d2303bf1d533541f65a92d985f94f33b8e87b036d1R2-R37) These changes collectively deliver a new advanced tool for users to automatically rename PDFs, with robust parameter handling, user interface integration, and proper handling of filenames as determined by backend logic. --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --------- Co-authored-by: Connor Yoh <connor@stirlingpdf.com> Co-authored-by: ConnorYoh <40631091+ConnorYoh@users.noreply.github.com> Co-authored-by: Reece Browne <74901996+reecebrowne@users.noreply.github.com>
202 lines
7.6 KiB
TypeScript
202 lines
7.6 KiB
TypeScript
import axios from 'axios';
|
|
import { ToolRegistry } from '../data/toolsTaxonomy';
|
|
import { AUTOMATION_CONSTANTS } from '../constants/automation';
|
|
import { AutomationFileProcessor } from './automationFileProcessor';
|
|
import { ToolType } from '../hooks/tools/shared/useToolOperation';
|
|
import { processResponse } from './toolResponseProcessor';
|
|
|
|
|
|
/**
|
|
* 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 as keyof ToolRegistry]?.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 processResponse to respect preserveBackendFilename
|
|
const processedFiles = await processResponse(
|
|
response.data,
|
|
files,
|
|
filePrefix,
|
|
undefined,
|
|
config.preserveBackendFilename ? response.headers : undefined
|
|
);
|
|
result = {
|
|
success: true,
|
|
files: processedFiles,
|
|
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
|
|
// Skip prefixing if preserveBackendFilename is true and backend provided a filename
|
|
const processedFiles = filePrefix && !config.preserveBackendFilename
|
|
? 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 using processResponse to respect preserveBackendFilename setting
|
|
const processedFiles = await processResponse(
|
|
response.data,
|
|
[file],
|
|
filePrefix,
|
|
undefined,
|
|
config.preserveBackendFilename ? response.headers : undefined
|
|
);
|
|
resultFiles.push(...processedFiles);
|
|
console.log(`✅ Created result file(s): ${processedFiles.map(f => f.name).join(', ')}`);
|
|
}
|
|
|
|
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;
|
|
};
|