mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-26 14:19:24 +00:00
Thursday demo
This commit is contained in:
parent
4956d6b4da
commit
85caad5f5c
@ -99,6 +99,14 @@ export default function AutomationCreation({ mode, existingAutomation, onBack, o
|
|||||||
return tool?.name || t(`tools.${operation}.name`, operation);
|
return tool?.name || t(`tools.${operation}.name`, operation);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getToolDefaultParameters = (operation: string): any => {
|
||||||
|
const config = toolRegistry[operation]?.operationConfig;
|
||||||
|
if (config?.defaultParameters) {
|
||||||
|
return { ...config.defaultParameters }; // Return a copy to avoid mutations
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
const addTool = (operation: string) => {
|
const addTool = (operation: string) => {
|
||||||
|
|
||||||
const newTool: AutomationTool = {
|
const newTool: AutomationTool = {
|
||||||
@ -106,7 +114,7 @@ export default function AutomationCreation({ mode, existingAutomation, onBack, o
|
|||||||
operation,
|
operation,
|
||||||
name: getToolName(operation),
|
name: getToolName(operation),
|
||||||
configured: false,
|
configured: false,
|
||||||
parameters: {}
|
parameters: getToolDefaultParameters(operation)
|
||||||
};
|
};
|
||||||
|
|
||||||
setSelectedTools([...selectedTools, newTool]);
|
setSelectedTools([...selectedTools, newTool]);
|
||||||
@ -259,15 +267,7 @@ export default function AutomationCreation({ mode, existingAutomation, onBack, o
|
|||||||
const updatedTools = [...selectedTools];
|
const updatedTools = [...selectedTools];
|
||||||
|
|
||||||
// Get default parameters from the tool
|
// Get default parameters from the tool
|
||||||
let defaultParams = {};
|
const defaultParams = getToolDefaultParameters(newOperation);
|
||||||
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],
|
||||||
@ -370,6 +370,7 @@ export default function AutomationCreation({ mode, existingAutomation, onBack, o
|
|||||||
tool={currentConfigTool}
|
tool={currentConfigTool}
|
||||||
onSave={handleToolConfigSave}
|
onSave={handleToolConfigSave}
|
||||||
onCancel={handleToolConfigCancel}
|
onCancel={handleToolConfigCancel}
|
||||||
|
toolRegistry={toolRegistry}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -1,112 +0,0 @@
|
|||||||
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 ('tool' in toolComponent) {
|
|
||||||
// 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 (!('tool' in toolComponent)) {
|
|
||||||
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;
|
|
||||||
};
|
|
@ -1,24 +1,22 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Button, Text, Stack, Group, Progress, Card } from "@mantine/core";
|
import { Button, Text, Stack, Group, Card, Progress } from "@mantine/core";
|
||||||
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
|
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 { useFileSelection } from "../../../contexts/FileSelectionContext";
|
import { useFileSelection } from "../../../contexts/FileSelectionContext";
|
||||||
import { useFlatToolRegistry } from "../../../data/useTranslatedToolRegistry";
|
import { useFlatToolRegistry } from "../../../data/useTranslatedToolRegistry";
|
||||||
import { executeAutomationSequence } from "../../../utils/automationExecutor";
|
|
||||||
|
|
||||||
interface AutomationRunProps {
|
interface AutomationRunProps {
|
||||||
automation: any;
|
automation: any;
|
||||||
onComplete: () => void;
|
onComplete: () => void;
|
||||||
automateOperation?: any; // Add the operation hook to store results
|
automateOperation?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ExecutionStep {
|
interface ExecutionStep {
|
||||||
id: string;
|
id: string;
|
||||||
operation: string;
|
operation: string;
|
||||||
name: string;
|
name: string;
|
||||||
status: "pending" | "running" | "completed" | "error";
|
status: 'pending' | 'running' | 'completed' | 'error';
|
||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,10 +24,15 @@ export default function AutomationRun({ automation, onComplete, automateOperatio
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { selectedFiles } = useFileSelection();
|
const { selectedFiles } = useFileSelection();
|
||||||
const toolRegistry = useFlatToolRegistry();
|
const toolRegistry = useFlatToolRegistry();
|
||||||
const [isExecuting, setIsExecuting] = useState(false);
|
|
||||||
|
// Progress tracking state
|
||||||
const [executionSteps, setExecutionSteps] = useState<ExecutionStep[]>([]);
|
const [executionSteps, setExecutionSteps] = useState<ExecutionStep[]>([]);
|
||||||
const [currentStepIndex, setCurrentStepIndex] = useState(-1);
|
const [currentStepIndex, setCurrentStepIndex] = useState(-1);
|
||||||
|
|
||||||
|
// Use the operation hook's loading state
|
||||||
|
const isExecuting = automateOperation?.isLoading || false;
|
||||||
|
const hasResults = automateOperation?.files.length > 0 || automateOperation?.downloadUrl !== null;
|
||||||
|
|
||||||
// Initialize execution steps from automation
|
// Initialize execution steps from automation
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (automation?.operations) {
|
if (automation?.operations) {
|
||||||
@ -39,16 +42,25 @@ export default function AutomationRun({ automation, onComplete, automateOperatio
|
|||||||
id: `${op.operation}-${index}`,
|
id: `${op.operation}-${index}`,
|
||||||
operation: op.operation,
|
operation: op.operation,
|
||||||
name: tool?.name || op.operation,
|
name: tool?.name || op.operation,
|
||||||
status: "pending" as const,
|
status: 'pending' as const
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
setExecutionSteps(steps);
|
setExecutionSteps(steps);
|
||||||
|
setCurrentStepIndex(-1);
|
||||||
}
|
}
|
||||||
}, [automation]); // Remove toolRegistry from dependencies to prevent infinite loops
|
}, [automation, toolRegistry]);
|
||||||
|
|
||||||
|
// Cleanup when component unmounts
|
||||||
|
React.useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
// Reset progress state when component unmounts
|
||||||
|
setExecutionSteps([]);
|
||||||
|
setCurrentStepIndex(-1);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const executeAutomation = async () => {
|
const executeAutomation = async () => {
|
||||||
if (!selectedFiles || selectedFiles.length === 0) {
|
if (!selectedFiles || selectedFiles.length === 0) {
|
||||||
// Show error - need files to execute automation
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,59 +69,74 @@ export default function AutomationRun({ automation, onComplete, automateOperatio
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsExecuting(true);
|
// Reset progress tracking
|
||||||
setCurrentStepIndex(0);
|
setCurrentStepIndex(0);
|
||||||
|
setExecutionSteps(prev => prev.map(step => ({ ...step, status: 'pending' as const, error: undefined })));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Use the automateOperation.executeOperation to handle file consumption properly
|
// Use the automateOperation.executeOperation to handle file consumption properly
|
||||||
await automateOperation.executeOperation(
|
await automateOperation.executeOperation(
|
||||||
{ automationConfig: automation },
|
{
|
||||||
|
automationConfig: automation,
|
||||||
|
onStepStart: (stepIndex: number, operationName: string) => {
|
||||||
|
setCurrentStepIndex(stepIndex);
|
||||||
|
setExecutionSteps(prev => prev.map((step, idx) =>
|
||||||
|
idx === stepIndex ? { ...step, status: 'running' as const } : step
|
||||||
|
));
|
||||||
|
},
|
||||||
|
onStepComplete: (stepIndex: number, resultFiles: File[]) => {
|
||||||
|
setExecutionSteps(prev => prev.map((step, idx) =>
|
||||||
|
idx === stepIndex ? { ...step, status: 'completed' as const } : step
|
||||||
|
));
|
||||||
|
},
|
||||||
|
onStepError: (stepIndex: number, error: string) => {
|
||||||
|
setExecutionSteps(prev => prev.map((step, idx) =>
|
||||||
|
idx === stepIndex ? { ...step, status: 'error' as const, error } : step
|
||||||
|
));
|
||||||
|
}
|
||||||
|
},
|
||||||
selectedFiles
|
selectedFiles
|
||||||
);
|
);
|
||||||
|
|
||||||
// All steps completed successfully
|
// Mark all as completed and reset current step
|
||||||
setCurrentStepIndex(-1);
|
setCurrentStepIndex(-1);
|
||||||
setIsExecuting(false);
|
|
||||||
|
|
||||||
console.log(`✅ Automation completed successfully`);
|
console.log(`✅ Automation completed successfully`);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("Automation execution failed:", error);
|
console.error("Automation execution failed:", error);
|
||||||
setIsExecuting(false);
|
|
||||||
setCurrentStepIndex(-1);
|
setCurrentStepIndex(-1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getStepIcon = (step: ExecutionStep) => {
|
|
||||||
switch (step.status) {
|
|
||||||
case "completed":
|
|
||||||
return <CheckIcon style={{ fontSize: 16, color: "green" }} />;
|
|
||||||
case "error":
|
|
||||||
return <ErrorIcon style={{ fontSize: 16, color: "red" }} />;
|
|
||||||
case "running":
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
border: "2px solid #ccc",
|
|
||||||
borderTop: "2px solid #007bff",
|
|
||||||
borderRadius: "50%",
|
|
||||||
animation: "spin 1s linear infinite",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return <div style={{ width: 16, height: 16, border: "2px solid #ccc", borderRadius: "50%" }} />;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getProgress = () => {
|
const getProgress = () => {
|
||||||
const completedSteps = executionSteps.filter((step) => step.status === "completed").length;
|
if (executionSteps.length === 0) return 0;
|
||||||
|
const completedSteps = executionSteps.filter(step => step.status === 'completed').length;
|
||||||
return (completedSteps / executionSteps.length) * 100;
|
return (completedSteps / executionSteps.length) * 100;
|
||||||
};
|
};
|
||||||
|
|
||||||
const allStepsCompleted = executionSteps.every((step) => step.status === "completed");
|
const getStepIcon = (step: ExecutionStep) => {
|
||||||
const hasErrors = executionSteps.some((step) => step.status === "error");
|
switch (step.status) {
|
||||||
|
case 'completed':
|
||||||
|
return <CheckIcon style={{ fontSize: 16, color: 'green' }} />;
|
||||||
|
case 'error':
|
||||||
|
return <span style={{ fontSize: 16, color: 'red' }}>✕</span>;
|
||||||
|
case 'running':
|
||||||
|
return <div style={{
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
border: '2px solid #ccc',
|
||||||
|
borderTop: '2px solid #007bff',
|
||||||
|
borderRadius: '50%',
|
||||||
|
animation: 'spin 1s linear infinite'
|
||||||
|
}} />;
|
||||||
|
default:
|
||||||
|
return <div style={{
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
border: '2px solid #ccc',
|
||||||
|
borderRadius: '50%'
|
||||||
|
}} />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -128,10 +155,7 @@ export default function AutomationRun({ automation, onComplete, automateOperatio
|
|||||||
{isExecuting && (
|
{isExecuting && (
|
||||||
<div>
|
<div>
|
||||||
<Text size="sm" mb="xs">
|
<Text size="sm" mb="xs">
|
||||||
{t("automate.sequence.progress", "Progress: {{current}}/{{total}}", {
|
Progress: {currentStepIndex + 1}/{executionSteps.length}
|
||||||
current: currentStepIndex + 1,
|
|
||||||
total: executionSteps.length,
|
|
||||||
})}
|
|
||||||
</Text>
|
</Text>
|
||||||
<Progress value={getProgress()} size="lg" />
|
<Progress value={getProgress()} size="lg" />
|
||||||
</div>
|
</div>
|
||||||
@ -151,8 +175,8 @@ export default function AutomationRun({ automation, onComplete, automateOperatio
|
|||||||
<Text
|
<Text
|
||||||
size="sm"
|
size="sm"
|
||||||
style={{
|
style={{
|
||||||
color: step.status === "running" ? "var(--mantine-color-blue-6)" : "var(--mantine-color-text)",
|
color: step.status === 'running' ? 'var(--mantine-color-blue-6)' : 'var(--mantine-color-text)',
|
||||||
fontWeight: step.status === "running" ? 500 : 400,
|
fontWeight: step.status === 'running' ? 500 : 400
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{step.name}
|
{step.name}
|
||||||
@ -179,6 +203,12 @@ export default function AutomationRun({ automation, onComplete, automateOperatio
|
|||||||
? t("automate.sequence.running", "Running Automation...")
|
? t("automate.sequence.running", "Running Automation...")
|
||||||
: t("automate.sequence.run", "Run Automation")}
|
: t("automate.sequence.run", "Run Automation")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
{hasResults && (
|
||||||
|
<Button variant="light" onClick={onComplete}>
|
||||||
|
{t("automate.sequence.finish", "Finish")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
@ -7,13 +7,13 @@ import {
|
|||||||
Group,
|
Group,
|
||||||
Stack,
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
Alert,
|
Alert
|
||||||
Loader
|
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import SettingsIcon from '@mui/icons-material/Settings';
|
import SettingsIcon from '@mui/icons-material/Settings';
|
||||||
import CheckIcon from '@mui/icons-material/Check';
|
import CheckIcon from '@mui/icons-material/Check';
|
||||||
import CloseIcon from '@mui/icons-material/Close';
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
import WarningIcon from '@mui/icons-material/Warning';
|
import WarningIcon from '@mui/icons-material/Warning';
|
||||||
|
import { ToolRegistry } from '../../../data/toolsTaxonomy';
|
||||||
interface ToolConfigurationModalProps {
|
interface ToolConfigurationModalProps {
|
||||||
opened: boolean;
|
opened: boolean;
|
||||||
tool: {
|
tool: {
|
||||||
@ -24,136 +24,31 @@ interface ToolConfigurationModalProps {
|
|||||||
};
|
};
|
||||||
onSave: (parameters: any) => void;
|
onSave: (parameters: any) => void;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
|
toolRegistry: ToolRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ToolConfigurationModal({ opened, tool, onSave, onCancel }: ToolConfigurationModalProps) {
|
export default function ToolConfigurationModal({ opened, tool, onSave, onCancel, toolRegistry }: ToolConfigurationModalProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [parameters, setParameters] = useState<any>({});
|
const [parameters, setParameters] = useState<any>({});
|
||||||
const [isValid, setIsValid] = useState(true);
|
const [isValid, setIsValid] = useState(true);
|
||||||
const [SettingsComponent, setSettingsComponent] = useState<React.ComponentType<any> | null>(null);
|
|
||||||
const [parameterHook, setParameterHook] = useState<any>(null);
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
|
|
||||||
// Dynamically load the settings component and parameter hook based on tool
|
// Get tool info from registry
|
||||||
useEffect(() => {
|
const toolInfo = toolRegistry[tool.operation];
|
||||||
const loadToolComponents = async () => {
|
const SettingsComponent = toolInfo?.settingsComponent;
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
try {
|
// Initialize parameters from tool (which should contain defaults from registry)
|
||||||
let settingsModule, parameterModule;
|
|
||||||
|
|
||||||
switch (tool.operation) {
|
|
||||||
case 'compress':
|
|
||||||
[settingsModule, parameterModule] = await Promise.all([
|
|
||||||
import('../compress/CompressSettings'),
|
|
||||||
import('../../../hooks/tools/compress/useCompressParameters')
|
|
||||||
]);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'split':
|
|
||||||
[settingsModule, parameterModule] = await Promise.all([
|
|
||||||
import('../split/SplitSettings'),
|
|
||||||
import('../../../hooks/tools/split/useSplitParameters')
|
|
||||||
]);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'addPassword':
|
|
||||||
[settingsModule, parameterModule] = await Promise.all([
|
|
||||||
import('../addPassword/AddPasswordSettings'),
|
|
||||||
import('../../../hooks/tools/addPassword/useAddPasswordParameters')
|
|
||||||
]);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'removePassword':
|
|
||||||
[settingsModule, parameterModule] = await Promise.all([
|
|
||||||
import('../removePassword/RemovePasswordSettings'),
|
|
||||||
import('../../../hooks/tools/removePassword/useRemovePasswordParameters')
|
|
||||||
]);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'changePermissions':
|
|
||||||
[settingsModule, parameterModule] = await Promise.all([
|
|
||||||
import('../changePermissions/ChangePermissionsSettings'),
|
|
||||||
import('../../../hooks/tools/changePermissions/useChangePermissionsParameters')
|
|
||||||
]);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'sanitize':
|
|
||||||
[settingsModule, parameterModule] = await Promise.all([
|
|
||||||
import('../sanitize/SanitizeSettings'),
|
|
||||||
import('../../../hooks/tools/sanitize/useSanitizeParameters')
|
|
||||||
]);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'ocr':
|
|
||||||
[settingsModule, parameterModule] = await Promise.all([
|
|
||||||
import('../ocr/OCRSettings'),
|
|
||||||
import('../../../hooks/tools/ocr/useOCRParameters')
|
|
||||||
]);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'convert':
|
|
||||||
[settingsModule, parameterModule] = await Promise.all([
|
|
||||||
import('../convert/ConvertSettings'),
|
|
||||||
import('../../../hooks/tools/convert/useConvertParameters')
|
|
||||||
]);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
setSettingsComponent(null);
|
|
||||||
setParameterHook(null);
|
|
||||||
setLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setSettingsComponent(() => settingsModule.default);
|
|
||||||
setParameterHook(() => parameterModule);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error loading components for ${tool.operation}:`, error);
|
|
||||||
setSettingsComponent(null);
|
|
||||||
setParameterHook(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (opened && tool.operation) {
|
|
||||||
loadToolComponents();
|
|
||||||
}
|
|
||||||
}, [opened, tool.operation]);
|
|
||||||
|
|
||||||
// Initialize parameters from tool or use defaults from hook
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (tool.parameters) {
|
if (tool.parameters) {
|
||||||
setParameters(tool.parameters);
|
setParameters(tool.parameters);
|
||||||
} else if (parameterHook) {
|
|
||||||
// If we have a parameter module, use its default parameters
|
|
||||||
try {
|
|
||||||
const defaultParams = parameterHook.defaultParameters || {};
|
|
||||||
setParameters(defaultParams);
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(`Error getting default parameters for ${tool.operation}:`, error);
|
|
||||||
setParameters({});
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
|
// Fallback to empty parameters if none provided
|
||||||
setParameters({});
|
setParameters({});
|
||||||
}
|
}
|
||||||
}, [tool.parameters, parameterHook, tool.operation]);
|
}, [tool.parameters, tool.operation]);
|
||||||
|
|
||||||
// Render the settings component
|
// Render the settings component
|
||||||
const renderToolSettings = () => {
|
const renderToolSettings = () => {
|
||||||
if (loading) {
|
|
||||||
return (
|
|
||||||
<Stack align="center" gap="md" py="xl">
|
|
||||||
<Loader size="md" />
|
|
||||||
<Text size="sm" c="dimmed">
|
|
||||||
{t('automate.config.loading', 'Loading tool configuration...')}
|
|
||||||
</Text>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!SettingsComponent) {
|
if (!SettingsComponent) {
|
||||||
return (
|
return (
|
||||||
<Alert icon={<WarningIcon />} color="orange">
|
<Alert icon={<WarningIcon />} color="orange">
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
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';
|
import { ToolOperationHook, ToolOperationConfig } from '../hooks/tools/shared/useToolOperation';
|
||||||
import { BaseToolProps } from '../types/tool';
|
import { BaseToolProps } from '../types/tool';
|
||||||
|
import { BaseParameters } from '../types/parameters';
|
||||||
|
|
||||||
export enum SubcategoryId {
|
export enum SubcategoryId {
|
||||||
SIGNING = 'signing',
|
SIGNING = 'signing',
|
||||||
@ -36,8 +37,10 @@ export type ToolRegistryEntry = {
|
|||||||
endpoints?: string[];
|
endpoints?: string[];
|
||||||
link?: string;
|
link?: string;
|
||||||
type?: string;
|
type?: string;
|
||||||
// Hook for automation execution
|
// Operation configuration for automation
|
||||||
operationHook?: () => ToolOperationHook<any>;
|
operationConfig?: ToolOperationConfig<any>;
|
||||||
|
// Settings component for automation configuration
|
||||||
|
settingsComponent?: React.ComponentType<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ToolRegistry = Record<string, ToolRegistryEntry>;
|
export type ToolRegistry = Record<string, ToolRegistryEntry>;
|
||||||
|
@ -14,6 +14,12 @@ import Repair from '../tools/Repair';
|
|||||||
import SingleLargePage from '../tools/SingleLargePage';
|
import SingleLargePage from '../tools/SingleLargePage';
|
||||||
import UnlockPdfForms from '../tools/UnlockPdfForms';
|
import UnlockPdfForms from '../tools/UnlockPdfForms';
|
||||||
import RemoveCertificateSign from '../tools/RemoveCertificateSign';
|
import RemoveCertificateSign from '../tools/RemoveCertificateSign';
|
||||||
|
import { compressOperationConfig } from '../hooks/tools/compress/useCompressOperation';
|
||||||
|
import { splitOperationConfig } from '../hooks/tools/split/useSplitOperation';
|
||||||
|
import { addPasswordOperationConfig } from '../hooks/tools/addPassword/useAddPasswordOperation';
|
||||||
|
import CompressSettings from '../components/tools/compress/CompressSettings';
|
||||||
|
import SplitSettings from '../components/tools/split/SplitSettings';
|
||||||
|
import AddPasswordSettings from '../components/tools/addPassword/AddPasswordSettings';
|
||||||
|
|
||||||
const showPlaceholderTools = false; // For development purposes. Allows seeing the full list of tools, even if they're unimplemented
|
const showPlaceholderTools = false; // For development purposes. Allows seeing the full list of tools, even if they're unimplemented
|
||||||
|
|
||||||
@ -56,7 +62,9 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
category: ToolCategory.STANDARD_TOOLS,
|
category: ToolCategory.STANDARD_TOOLS,
|
||||||
subcategory: SubcategoryId.DOCUMENT_SECURITY,
|
subcategory: SubcategoryId.DOCUMENT_SECURITY,
|
||||||
maxFiles: -1,
|
maxFiles: -1,
|
||||||
endpoints: ["add-password"]
|
endpoints: ["add-password"],
|
||||||
|
operationConfig: addPasswordOperationConfig,
|
||||||
|
settingsComponent: AddPasswordSettings
|
||||||
},
|
},
|
||||||
"watermark": {
|
"watermark": {
|
||||||
icon: <span className="material-symbols-rounded">branding_watermark</span>,
|
icon: <span className="material-symbols-rounded">branding_watermark</span>,
|
||||||
@ -198,7 +206,9 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
view: "split",
|
view: "split",
|
||||||
description: t("home.split.desc", "Split PDFs into multiple documents"),
|
description: t("home.split.desc", "Split PDFs into multiple documents"),
|
||||||
category: ToolCategory.STANDARD_TOOLS,
|
category: ToolCategory.STANDARD_TOOLS,
|
||||||
subcategory: SubcategoryId.PAGE_FORMATTING
|
subcategory: SubcategoryId.PAGE_FORMATTING,
|
||||||
|
operationConfig: splitOperationConfig,
|
||||||
|
settingsComponent: SplitSettings
|
||||||
},
|
},
|
||||||
"reorganize-pages": {
|
"reorganize-pages": {
|
||||||
icon: <span className="material-symbols-rounded">move_down</span>,
|
icon: <span className="material-symbols-rounded">move_down</span>,
|
||||||
@ -534,7 +544,9 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
description: t("home.compress.desc", "Compress PDFs to reduce their file size."),
|
description: t("home.compress.desc", "Compress PDFs to reduce their file size."),
|
||||||
category: ToolCategory.RECOMMENDED_TOOLS,
|
category: ToolCategory.RECOMMENDED_TOOLS,
|
||||||
subcategory: SubcategoryId.GENERAL,
|
subcategory: SubcategoryId.GENERAL,
|
||||||
maxFiles: -1
|
maxFiles: -1,
|
||||||
|
operationConfig: compressOperationConfig,
|
||||||
|
settingsComponent: CompressSettings
|
||||||
},
|
},
|
||||||
"convert": {
|
"convert": {
|
||||||
icon: <span className="material-symbols-rounded">sync_alt</span>,
|
icon: <span className="material-symbols-rounded">sync_alt</span>,
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useToolOperation } from '../shared/useToolOperation';
|
import { useToolOperation } from '../shared/useToolOperation';
|
||||||
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
|
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
|
||||||
import { AddPasswordFullParameters } from './useAddPasswordParameters';
|
import { AddPasswordFullParameters, defaultParameters } from './useAddPasswordParameters';
|
||||||
|
import { defaultParameters as permissionsDefaults } from '../changePermissions/useChangePermissionsParameters';
|
||||||
import { getFormData } from '../changePermissions/useChangePermissionsOperation';
|
import { getFormData } from '../changePermissions/useChangePermissionsOperation';
|
||||||
|
|
||||||
export const useAddPasswordOperation = () => {
|
// Static function that can be used by both the hook and automation executor
|
||||||
const { t } = useTranslation();
|
export const buildAddPasswordFormData = (parameters: AddPasswordFullParameters, file: File): FormData => {
|
||||||
|
|
||||||
const buildFormData = (parameters: AddPasswordFullParameters, file: File): FormData => {
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("fileInput", file);
|
formData.append("fileInput", file);
|
||||||
formData.append("password", parameters.password);
|
formData.append("password", parameters.password);
|
||||||
@ -17,14 +16,30 @@ export const useAddPasswordOperation = () => {
|
|||||||
formData.append(key, value);
|
formData.append(key, value);
|
||||||
});
|
});
|
||||||
return formData;
|
return formData;
|
||||||
};
|
};
|
||||||
|
|
||||||
return useToolOperation<AddPasswordFullParameters>({
|
// Full default parameters including permissions for automation
|
||||||
|
const fullDefaultParameters: AddPasswordFullParameters = {
|
||||||
|
...defaultParameters,
|
||||||
|
permissions: permissionsDefaults,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Static configuration object
|
||||||
|
export const addPasswordOperationConfig = {
|
||||||
operationType: 'addPassword',
|
operationType: 'addPassword',
|
||||||
endpoint: '/api/v1/security/add-password',
|
endpoint: '/api/v1/security/add-password',
|
||||||
buildFormData,
|
buildFormData: buildAddPasswordFormData,
|
||||||
filePrefix: t('addPassword.filenamePrefix', 'encrypted') + '_',
|
filePrefix: 'encrypted_', // Will be overridden in hook with translation
|
||||||
multiFileEndpoint: false,
|
multiFileEndpoint: false,
|
||||||
|
defaultParameters: fullDefaultParameters,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const useAddPasswordOperation = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return useToolOperation<AddPasswordFullParameters>({
|
||||||
|
...addPasswordOperationConfig,
|
||||||
|
filePrefix: t('addPassword.filenamePrefix', 'encrypted') + '_',
|
||||||
getErrorMessage: createStandardErrorHandler(t('addPassword.error.failed', 'An error occurred while encrypting the PDF.'))
|
getErrorMessage: createStandardErrorHandler(t('addPassword.error.failed', 'An error occurred while encrypting the PDF.'))
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,12 +1,18 @@
|
|||||||
import { useToolOperation } from '../shared/useToolOperation';
|
import { useToolOperation } from '../shared/useToolOperation';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { executeAutomationSequence } from '../../../utils/automationExecutor';
|
import { executeAutomationSequence } from '../../../utils/automationExecutor';
|
||||||
|
import { useFlatToolRegistry } from '../../../data/useTranslatedToolRegistry';
|
||||||
|
|
||||||
interface AutomateParameters {
|
interface AutomateParameters {
|
||||||
automationConfig?: any;
|
automationConfig?: any;
|
||||||
|
onStepStart?: (stepIndex: number, operationName: string) => void;
|
||||||
|
onStepComplete?: (stepIndex: number, resultFiles: File[]) => void;
|
||||||
|
onStepError?: (stepIndex: number, error: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useAutomateOperation() {
|
export function useAutomateOperation() {
|
||||||
|
const toolRegistry = useFlatToolRegistry();
|
||||||
|
|
||||||
const customProcessor = useCallback(async (params: AutomateParameters, files: File[]) => {
|
const customProcessor = useCallback(async (params: AutomateParameters, files: File[]) => {
|
||||||
console.log('🚀 Starting automation execution via customProcessor', { params, files });
|
console.log('🚀 Starting automation execution via customProcessor', { params, files });
|
||||||
|
|
||||||
@ -18,21 +24,25 @@ export function useAutomateOperation() {
|
|||||||
const finalResults = await executeAutomationSequence(
|
const finalResults = await executeAutomationSequence(
|
||||||
params.automationConfig,
|
params.automationConfig,
|
||||||
files,
|
files,
|
||||||
|
toolRegistry,
|
||||||
(stepIndex: number, operationName: string) => {
|
(stepIndex: number, operationName: string) => {
|
||||||
console.log(`Step ${stepIndex + 1} started: ${operationName}`);
|
console.log(`Step ${stepIndex + 1} started: ${operationName}`);
|
||||||
|
params.onStepStart?.(stepIndex, operationName);
|
||||||
},
|
},
|
||||||
(stepIndex: number, resultFiles: File[]) => {
|
(stepIndex: number, resultFiles: File[]) => {
|
||||||
console.log(`Step ${stepIndex + 1} completed with ${resultFiles.length} files`);
|
console.log(`Step ${stepIndex + 1} completed with ${resultFiles.length} files`);
|
||||||
|
params.onStepComplete?.(stepIndex, resultFiles);
|
||||||
},
|
},
|
||||||
(stepIndex: number, error: string) => {
|
(stepIndex: number, error: string) => {
|
||||||
console.error(`Step ${stepIndex + 1} failed:`, error);
|
console.error(`Step ${stepIndex + 1} failed:`, error);
|
||||||
|
params.onStepError?.(stepIndex, error);
|
||||||
throw new Error(`Automation step ${stepIndex + 1} failed: ${error}`);
|
throw new Error(`Automation step ${stepIndex + 1} failed: ${error}`);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(`✅ Automation completed, returning ${finalResults.length} files`);
|
console.log(`✅ Automation completed, returning ${finalResults.length} files`);
|
||||||
return finalResults;
|
return finalResults;
|
||||||
}, []);
|
}, [toolRegistry]);
|
||||||
|
|
||||||
return useToolOperation<AutomateParameters>({
|
return useToolOperation<AutomateParameters>({
|
||||||
operationType: 'automate',
|
operationType: 'automate',
|
||||||
|
@ -1,141 +0,0 @@
|
|||||||
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: ExecutionStep) => {
|
|
||||||
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
|
|
||||||
};
|
|
||||||
};
|
|
@ -1,9 +1,10 @@
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useToolOperation } from '../shared/useToolOperation';
|
import { useToolOperation, ToolOperationConfig } from '../shared/useToolOperation';
|
||||||
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
|
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
|
||||||
import { CompressParameters } from './useCompressParameters';
|
import { CompressParameters, defaultParameters } from './useCompressParameters';
|
||||||
|
|
||||||
const buildFormData = (parameters: CompressParameters, file: File): FormData => {
|
// Static configuration that can be used by both the hook and automation executor
|
||||||
|
export const buildCompressFormData = (parameters: CompressParameters, file: File): FormData => {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("fileInput", file);
|
formData.append("fileInput", file);
|
||||||
|
|
||||||
@ -21,15 +22,21 @@ const buildFormData = (parameters: CompressParameters, file: File): FormData =>
|
|||||||
return formData;
|
return formData;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Static configuration object
|
||||||
|
export const compressOperationConfig = {
|
||||||
|
operationType: 'compress',
|
||||||
|
endpoint: '/api/v1/misc/compress-pdf',
|
||||||
|
buildFormData: buildCompressFormData,
|
||||||
|
filePrefix: 'compressed_',
|
||||||
|
multiFileEndpoint: false, // Individual API calls per file
|
||||||
|
defaultParameters,
|
||||||
|
} as const;
|
||||||
|
|
||||||
export const useCompressOperation = () => {
|
export const useCompressOperation = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return useToolOperation<CompressParameters>({
|
return useToolOperation<CompressParameters>({
|
||||||
operationType: 'compress',
|
...compressOperationConfig,
|
||||||
endpoint: '/api/v1/misc/compress-pdf',
|
|
||||||
buildFormData,
|
|
||||||
filePrefix: 'compressed_',
|
|
||||||
multiFileEndpoint: false, // Individual API calls per file
|
|
||||||
getErrorMessage: createStandardErrorHandler(t('compress.error.failed', 'An error occurred while compressing the PDF.'))
|
getErrorMessage: createStandardErrorHandler(t('compress.error.failed', 'An error occurred while compressing the PDF.'))
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -10,7 +10,7 @@ export interface CompressParameters extends BaseParameters {
|
|||||||
fileSizeUnit: 'KB' | 'MB';
|
fileSizeUnit: 'KB' | 'MB';
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultParameters: CompressParameters = {
|
export const defaultParameters: CompressParameters = {
|
||||||
compressionLevel: 5,
|
compressionLevel: 5,
|
||||||
grayscale: false,
|
grayscale: false,
|
||||||
expectedSize: '',
|
expectedSize: '',
|
||||||
|
@ -61,6 +61,9 @@ export interface ToolOperationConfig<TParams = void> {
|
|||||||
|
|
||||||
/** Extract user-friendly error messages from API errors */
|
/** Extract user-friendly error messages from API errors */
|
||||||
getErrorMessage?: (error: any) => string;
|
getErrorMessage?: (error: any) => string;
|
||||||
|
|
||||||
|
/** Default parameter values for automation */
|
||||||
|
defaultParameters?: TParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useToolOperation } from '../shared/useToolOperation';
|
import { useToolOperation } from '../shared/useToolOperation';
|
||||||
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
|
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
|
||||||
import { SplitParameters } from './useSplitParameters';
|
import { SplitParameters, defaultParameters } from './useSplitParameters';
|
||||||
import { SPLIT_MODES } from '../../../constants/splitConstants';
|
import { SPLIT_MODES } from '../../../constants/splitConstants';
|
||||||
|
|
||||||
|
// Static functions that can be used by both the hook and automation executor
|
||||||
const buildFormData = (parameters: SplitParameters, selectedFiles: File[]): FormData => {
|
export const buildSplitFormData = (parameters: SplitParameters, selectedFiles: File[]): FormData => {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
|
||||||
selectedFiles.forEach(file => {
|
selectedFiles.forEach(file => {
|
||||||
@ -40,7 +40,7 @@ const buildFormData = (parameters: SplitParameters, selectedFiles: File[]): Form
|
|||||||
return formData;
|
return formData;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getEndpoint = (parameters: SplitParameters): string => {
|
export const getSplitEndpoint = (parameters: SplitParameters): string => {
|
||||||
switch (parameters.mode) {
|
switch (parameters.mode) {
|
||||||
case SPLIT_MODES.BY_PAGES:
|
case SPLIT_MODES.BY_PAGES:
|
||||||
return "/api/v1/general/split-pages";
|
return "/api/v1/general/split-pages";
|
||||||
@ -55,15 +55,21 @@ const getEndpoint = (parameters: SplitParameters): string => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Static configuration object
|
||||||
|
export const splitOperationConfig = {
|
||||||
|
operationType: 'splitPdf',
|
||||||
|
endpoint: getSplitEndpoint,
|
||||||
|
buildFormData: buildSplitFormData,
|
||||||
|
filePrefix: 'split_',
|
||||||
|
multiFileEndpoint: true, // Single API call with all files
|
||||||
|
defaultParameters,
|
||||||
|
} as const;
|
||||||
|
|
||||||
export const useSplitOperation = () => {
|
export const useSplitOperation = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return useToolOperation<SplitParameters>({
|
return useToolOperation<SplitParameters>({
|
||||||
operationType: 'split',
|
...splitOperationConfig,
|
||||||
endpoint: (params) => getEndpoint(params),
|
|
||||||
buildFormData: buildFormData, // Multi-file signature: (params, selectedFiles) => FormData
|
|
||||||
filePrefix: 'split_',
|
|
||||||
multiFileEndpoint: true, // Single API call with all files
|
|
||||||
getErrorMessage: createStandardErrorHandler(t('split.error.failed', 'An error occurred while splitting the PDF.'))
|
getErrorMessage: createStandardErrorHandler(t('split.error.failed', 'An error occurred while splitting the PDF.'))
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -17,7 +17,7 @@ export interface SplitParameters extends BaseParameters {
|
|||||||
|
|
||||||
export type SplitParametersHook = BaseParametersHook<SplitParameters>;
|
export type SplitParametersHook = BaseParametersHook<SplitParameters>;
|
||||||
|
|
||||||
const defaultParameters: SplitParameters = {
|
export const defaultParameters: SplitParameters = {
|
||||||
mode: '',
|
mode: '',
|
||||||
pages: '',
|
pages: '',
|
||||||
hDiv: '2',
|
hDiv: '2',
|
||||||
|
@ -114,12 +114,4 @@ const AddPassword = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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;
|
export default AddPassword as ToolComponent;
|
||||||
|
@ -25,14 +25,28 @@ const Automate = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
|||||||
const automateOperation = useAutomateOperation();
|
const automateOperation = useAutomateOperation();
|
||||||
const toolRegistry = useFlatToolRegistry();
|
const toolRegistry = useFlatToolRegistry();
|
||||||
const hasResults = automateOperation.files.length > 0 || automateOperation.downloadUrl !== null;
|
const hasResults = automateOperation.files.length > 0 || automateOperation.downloadUrl !== null;
|
||||||
const { savedAutomations, deleteAutomation } = useSavedAutomations();
|
const { savedAutomations, deleteAutomation, refreshAutomations } = useSavedAutomations();
|
||||||
|
|
||||||
const handleStepChange = (data: any) => {
|
const handleStepChange = (data: any) => {
|
||||||
|
// If navigating away from run step, reset automation results
|
||||||
|
if (currentStep === 'run' && data.step !== 'run') {
|
||||||
|
automateOperation.resetResults();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If navigating to run step with a different automation, reset results
|
||||||
|
if (data.step === 'run' && data.automation &&
|
||||||
|
stepData.automation && data.automation.id !== stepData.automation.id) {
|
||||||
|
automateOperation.resetResults();
|
||||||
|
}
|
||||||
|
|
||||||
setStepData(data);
|
setStepData(data);
|
||||||
setCurrentStep(data.step);
|
setCurrentStep(data.step);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleComplete = () => {
|
const handleComplete = () => {
|
||||||
|
// Reset automation results when completing
|
||||||
|
automateOperation.resetResults();
|
||||||
|
|
||||||
// Reset to selection step
|
// Reset to selection step
|
||||||
setCurrentStep('selection');
|
setCurrentStep('selection');
|
||||||
setStepData({});
|
setStepData({});
|
||||||
@ -65,7 +79,10 @@ const Automate = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
|||||||
mode={stepData.mode}
|
mode={stepData.mode}
|
||||||
existingAutomation={stepData.automation}
|
existingAutomation={stepData.automation}
|
||||||
onBack={() => handleStepChange({ step: 'selection' })}
|
onBack={() => handleStepChange({ step: 'selection' })}
|
||||||
onComplete={() => handleStepChange({ step: 'selection' })}
|
onComplete={() => {
|
||||||
|
refreshAutomations();
|
||||||
|
handleStepChange({ step: 'selection' });
|
||||||
|
}}
|
||||||
toolRegistry={toolRegistry}
|
toolRegistry={toolRegistry}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -95,10 +95,5 @@ const Compress = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Static method to get the operation hook for automation
|
|
||||||
Compress.tool = () => useCompressOperation;
|
|
||||||
|
|
||||||
// Static method to get default parameters for automation
|
|
||||||
Compress.getDefaultParameters = () => useCompressParameters();
|
|
||||||
|
|
||||||
export default Compress as ToolComponent;
|
export default Compress as ToolComponent;
|
||||||
|
@ -92,7 +92,4 @@ const Split = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Static method to get the operation hook for automation
|
|
||||||
Split.tool = () => useSplitOperation;
|
|
||||||
|
|
||||||
export default Split as ToolComponent;
|
export default Split as ToolComponent;
|
||||||
|
@ -1,76 +1,31 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import { ToolRegistry } from '../data/toolsTaxonomy';
|
||||||
// Tool operation configurations extracted from the hook implementations
|
import { zipFileService } from '../services/zipFileService';
|
||||||
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
|
* Extract zip files from response blob
|
||||||
*/
|
*/
|
||||||
const extractZipFiles = async (blob: Blob): Promise<File[]> => {
|
const extractZipFiles = async (blob: Blob): Promise<File[]> => {
|
||||||
// This would need the actual zip extraction logic from the codebase
|
try {
|
||||||
// For now, create a single file from the blob
|
// Convert blob to File for the zip service
|
||||||
|
const zipFile = new File([blob], `response_${Date.now()}.zip`, { type: 'application/zip' });
|
||||||
|
|
||||||
|
// Extract PDF files from the ZIP
|
||||||
|
const result = await zipFileService.extractPdfFiles(zipFile);
|
||||||
|
|
||||||
|
if (!result.success || result.extractedFiles.length === 0) {
|
||||||
|
console.error('ZIP extraction failed:', result.errors);
|
||||||
|
throw new Error(`ZIP extraction failed: ${result.errors.join(', ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`📦 Extracted ${result.extractedFiles.length} files from ZIP`);
|
||||||
|
return result.extractedFiles;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to extract ZIP files:', error);
|
||||||
|
// Fallback: treat as single PDF file
|
||||||
const file = new File([blob], `result_${Date.now()}.pdf`, { type: 'application/pdf' });
|
const file = new File([blob], `result_${Date.now()}.pdf`, { type: 'application/pdf' });
|
||||||
return [file];
|
return [file];
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -79,11 +34,12 @@ const extractZipFiles = async (blob: Blob): Promise<File[]> => {
|
|||||||
export const executeToolOperation = async (
|
export const executeToolOperation = async (
|
||||||
operationName: string,
|
operationName: string,
|
||||||
parameters: any,
|
parameters: any,
|
||||||
files: File[]
|
files: File[],
|
||||||
|
toolRegistry: ToolRegistry
|
||||||
): Promise<File[]> => {
|
): Promise<File[]> => {
|
||||||
console.log(`🔧 Executing tool: ${operationName}`, { parameters, fileCount: files.length });
|
console.log(`🔧 Executing tool: ${operationName}`, { parameters, fileCount: files.length });
|
||||||
|
|
||||||
const config = TOOL_CONFIGS[operationName];
|
const config = toolRegistry[operationName]?.operationConfig;
|
||||||
if (!config) {
|
if (!config) {
|
||||||
console.error(`❌ Tool operation not supported: ${operationName}`);
|
console.error(`❌ Tool operation not supported: ${operationName}`);
|
||||||
throw new Error(`Tool operation not supported: ${operationName}`);
|
throw new Error(`Tool operation not supported: ${operationName}`);
|
||||||
@ -99,7 +55,7 @@ export const executeToolOperation = async (
|
|||||||
: config.endpoint;
|
: config.endpoint;
|
||||||
|
|
||||||
console.log(`🌐 Making multi-file request to: ${endpoint}`);
|
console.log(`🌐 Making multi-file request to: ${endpoint}`);
|
||||||
const formData = config.buildFormData(parameters, files);
|
const formData = (config.buildFormData as (params: any, files: File[]) => FormData)(parameters, files);
|
||||||
console.log(`📤 FormData entries:`, Array.from(formData.entries()));
|
console.log(`📤 FormData entries:`, Array.from(formData.entries()));
|
||||||
|
|
||||||
const response = await axios.post(endpoint, formData, {
|
const response = await axios.post(endpoint, formData, {
|
||||||
@ -126,7 +82,7 @@ export const executeToolOperation = async (
|
|||||||
: config.endpoint;
|
: config.endpoint;
|
||||||
|
|
||||||
console.log(`🌐 Making single-file request ${i+1}/${files.length} to: ${endpoint} for file: ${file.name}`);
|
console.log(`🌐 Making single-file request ${i+1}/${files.length} to: ${endpoint} for file: ${file.name}`);
|
||||||
const formData = config.buildFormData(parameters, file);
|
const formData = (config.buildFormData as (params: any, file: File) => FormData)(parameters, file);
|
||||||
console.log(`📤 FormData entries:`, Array.from(formData.entries()));
|
console.log(`📤 FormData entries:`, Array.from(formData.entries()));
|
||||||
|
|
||||||
const response = await axios.post(endpoint, formData, {
|
const response = await axios.post(endpoint, formData, {
|
||||||
@ -162,6 +118,7 @@ export const executeToolOperation = async (
|
|||||||
export const executeAutomationSequence = async (
|
export const executeAutomationSequence = async (
|
||||||
automation: any,
|
automation: any,
|
||||||
initialFiles: File[],
|
initialFiles: File[],
|
||||||
|
toolRegistry: ToolRegistry,
|
||||||
onStepStart?: (stepIndex: number, operationName: string) => void,
|
onStepStart?: (stepIndex: number, operationName: string) => void,
|
||||||
onStepComplete?: (stepIndex: number, resultFiles: File[]) => void,
|
onStepComplete?: (stepIndex: number, resultFiles: File[]) => void,
|
||||||
onStepError?: (stepIndex: number, error: string) => void
|
onStepError?: (stepIndex: number, error: string) => void
|
||||||
@ -189,7 +146,8 @@ export const executeAutomationSequence = async (
|
|||||||
const resultFiles = await executeToolOperation(
|
const resultFiles = await executeToolOperation(
|
||||||
operation.operation,
|
operation.operation,
|
||||||
operation.parameters || {},
|
operation.parameters || {},
|
||||||
currentFiles
|
currentFiles,
|
||||||
|
toolRegistry
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(`✅ Step ${i + 1} completed: ${resultFiles.length} result files`);
|
console.log(`✅ Step ${i + 1} completed: ${resultFiles.length} result files`);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user