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);
|
||||
};
|
||||
|
||||
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 newTool: AutomationTool = {
|
||||
@ -106,7 +114,7 @@ export default function AutomationCreation({ mode, existingAutomation, onBack, o
|
||||
operation,
|
||||
name: getToolName(operation),
|
||||
configured: false,
|
||||
parameters: {}
|
||||
parameters: getToolDefaultParameters(operation)
|
||||
};
|
||||
|
||||
setSelectedTools([...selectedTools, newTool]);
|
||||
@ -259,15 +267,7 @@ export default function AutomationCreation({ mode, existingAutomation, onBack, o
|
||||
const updatedTools = [...selectedTools];
|
||||
|
||||
// Get default parameters from the tool
|
||||
let defaultParams = {};
|
||||
const tool = toolRegistry?.[newOperation];
|
||||
if (tool?.component && (tool.component as any).getDefaultParameters) {
|
||||
try {
|
||||
defaultParams = (tool.component as any).getDefaultParameters();
|
||||
} catch (error) {
|
||||
console.warn(`Failed to get default parameters for ${newOperation}:`, error);
|
||||
}
|
||||
}
|
||||
const defaultParams = getToolDefaultParameters(newOperation);
|
||||
|
||||
updatedTools[index] = {
|
||||
...updatedTools[index],
|
||||
@ -370,6 +370,7 @@ export default function AutomationCreation({ mode, existingAutomation, onBack, o
|
||||
tool={currentConfigTool}
|
||||
onSave={handleToolConfigSave}
|
||||
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 { 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 CheckIcon from "@mui/icons-material/Check";
|
||||
import ErrorIcon from "@mui/icons-material/Error";
|
||||
import { useFileSelection } from "../../../contexts/FileSelectionContext";
|
||||
import { useFlatToolRegistry } from "../../../data/useTranslatedToolRegistry";
|
||||
import { executeAutomationSequence } from "../../../utils/automationExecutor";
|
||||
|
||||
interface AutomationRunProps {
|
||||
automation: any;
|
||||
onComplete: () => void;
|
||||
automateOperation?: any; // Add the operation hook to store results
|
||||
automateOperation?: any;
|
||||
}
|
||||
|
||||
interface ExecutionStep {
|
||||
id: string;
|
||||
operation: string;
|
||||
name: string;
|
||||
status: "pending" | "running" | "completed" | "error";
|
||||
status: 'pending' | 'running' | 'completed' | 'error';
|
||||
error?: string;
|
||||
}
|
||||
|
||||
@ -26,10 +24,15 @@ export default function AutomationRun({ automation, onComplete, automateOperatio
|
||||
const { t } = useTranslation();
|
||||
const { selectedFiles } = useFileSelection();
|
||||
const toolRegistry = useFlatToolRegistry();
|
||||
const [isExecuting, setIsExecuting] = useState(false);
|
||||
|
||||
// Progress tracking state
|
||||
const [executionSteps, setExecutionSteps] = useState<ExecutionStep[]>([]);
|
||||
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
|
||||
React.useEffect(() => {
|
||||
if (automation?.operations) {
|
||||
@ -39,16 +42,25 @@ export default function AutomationRun({ automation, onComplete, automateOperatio
|
||||
id: `${op.operation}-${index}`,
|
||||
operation: op.operation,
|
||||
name: tool?.name || op.operation,
|
||||
status: "pending" as const,
|
||||
status: 'pending' as const
|
||||
};
|
||||
});
|
||||
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 () => {
|
||||
if (!selectedFiles || selectedFiles.length === 0) {
|
||||
// Show error - need files to execute automation
|
||||
return;
|
||||
}
|
||||
|
||||
@ -57,59 +69,74 @@ export default function AutomationRun({ automation, onComplete, automateOperatio
|
||||
return;
|
||||
}
|
||||
|
||||
setIsExecuting(true);
|
||||
// Reset progress tracking
|
||||
setCurrentStepIndex(0);
|
||||
setExecutionSteps(prev => prev.map(step => ({ ...step, status: 'pending' as const, error: undefined })));
|
||||
|
||||
try {
|
||||
// Use the automateOperation.executeOperation to handle file consumption properly
|
||||
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
|
||||
);
|
||||
|
||||
// All steps completed successfully
|
||||
// Mark all as completed and reset current step
|
||||
setCurrentStepIndex(-1);
|
||||
setIsExecuting(false);
|
||||
|
||||
console.log(`✅ Automation completed successfully`);
|
||||
} catch (error: any) {
|
||||
console.error("Automation execution failed:", error);
|
||||
setIsExecuting(false);
|
||||
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 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;
|
||||
};
|
||||
|
||||
const allStepsCompleted = executionSteps.every((step) => step.status === "completed");
|
||||
const hasErrors = executionSteps.some((step) => step.status === "error");
|
||||
const getStepIcon = (step: ExecutionStep) => {
|
||||
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 (
|
||||
<div>
|
||||
@ -128,10 +155,7 @@ export default function AutomationRun({ automation, onComplete, automateOperatio
|
||||
{isExecuting && (
|
||||
<div>
|
||||
<Text size="sm" mb="xs">
|
||||
{t("automate.sequence.progress", "Progress: {{current}}/{{total}}", {
|
||||
current: currentStepIndex + 1,
|
||||
total: executionSteps.length,
|
||||
})}
|
||||
Progress: {currentStepIndex + 1}/{executionSteps.length}
|
||||
</Text>
|
||||
<Progress value={getProgress()} size="lg" />
|
||||
</div>
|
||||
@ -151,8 +175,8 @@ export default function AutomationRun({ automation, onComplete, automateOperatio
|
||||
<Text
|
||||
size="sm"
|
||||
style={{
|
||||
color: step.status === "running" ? "var(--mantine-color-blue-6)" : "var(--mantine-color-text)",
|
||||
fontWeight: step.status === "running" ? 500 : 400,
|
||||
color: step.status === 'running' ? 'var(--mantine-color-blue-6)' : 'var(--mantine-color-text)',
|
||||
fontWeight: step.status === 'running' ? 500 : 400
|
||||
}}
|
||||
>
|
||||
{step.name}
|
||||
@ -179,6 +203,12 @@ export default function AutomationRun({ automation, onComplete, automateOperatio
|
||||
? t("automate.sequence.running", "Running Automation...")
|
||||
: t("automate.sequence.run", "Run Automation")}
|
||||
</Button>
|
||||
|
||||
{hasResults && (
|
||||
<Button variant="light" onClick={onComplete}>
|
||||
{t("automate.sequence.finish", "Finish")}
|
||||
</Button>
|
||||
)}
|
||||
</Group>
|
||||
</Stack>
|
||||
|
||||
|
@ -7,13 +7,13 @@ import {
|
||||
Group,
|
||||
Stack,
|
||||
Text,
|
||||
Alert,
|
||||
Loader
|
||||
Alert
|
||||
} from '@mantine/core';
|
||||
import SettingsIcon from '@mui/icons-material/Settings';
|
||||
import CheckIcon from '@mui/icons-material/Check';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
import { ToolRegistry } from '../../../data/toolsTaxonomy';
|
||||
interface ToolConfigurationModalProps {
|
||||
opened: boolean;
|
||||
tool: {
|
||||
@ -24,136 +24,31 @@ interface ToolConfigurationModalProps {
|
||||
};
|
||||
onSave: (parameters: any) => 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 [parameters, setParameters] = useState<any>({});
|
||||
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
|
||||
useEffect(() => {
|
||||
const loadToolComponents = async () => {
|
||||
setLoading(true);
|
||||
// Get tool info from registry
|
||||
const toolInfo = toolRegistry[tool.operation];
|
||||
const SettingsComponent = toolInfo?.settingsComponent;
|
||||
|
||||
try {
|
||||
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
|
||||
// Initialize parameters from tool (which should contain defaults from registry)
|
||||
useEffect(() => {
|
||||
if (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 {
|
||||
// Fallback to empty parameters if none provided
|
||||
setParameters({});
|
||||
}
|
||||
}, [tool.parameters, parameterHook, tool.operation]);
|
||||
}, [tool.parameters, tool.operation]);
|
||||
|
||||
// Render the settings component
|
||||
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) {
|
||||
return (
|
||||
<Alert icon={<WarningIcon />} color="orange">
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { type TFunction } from 'i18next';
|
||||
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 { BaseParameters } from '../types/parameters';
|
||||
|
||||
export enum SubcategoryId {
|
||||
SIGNING = 'signing',
|
||||
@ -36,8 +37,10 @@ export type ToolRegistryEntry = {
|
||||
endpoints?: string[];
|
||||
link?: string;
|
||||
type?: string;
|
||||
// Hook for automation execution
|
||||
operationHook?: () => ToolOperationHook<any>;
|
||||
// Operation configuration for automation
|
||||
operationConfig?: ToolOperationConfig<any>;
|
||||
// Settings component for automation configuration
|
||||
settingsComponent?: React.ComponentType<any>;
|
||||
}
|
||||
|
||||
export type ToolRegistry = Record<string, ToolRegistryEntry>;
|
||||
|
@ -14,6 +14,12 @@ import Repair from '../tools/Repair';
|
||||
import SingleLargePage from '../tools/SingleLargePage';
|
||||
import UnlockPdfForms from '../tools/UnlockPdfForms';
|
||||
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
|
||||
|
||||
@ -56,7 +62,9 @@ export function useFlatToolRegistry(): ToolRegistry {
|
||||
category: ToolCategory.STANDARD_TOOLS,
|
||||
subcategory: SubcategoryId.DOCUMENT_SECURITY,
|
||||
maxFiles: -1,
|
||||
endpoints: ["add-password"]
|
||||
endpoints: ["add-password"],
|
||||
operationConfig: addPasswordOperationConfig,
|
||||
settingsComponent: AddPasswordSettings
|
||||
},
|
||||
"watermark": {
|
||||
icon: <span className="material-symbols-rounded">branding_watermark</span>,
|
||||
@ -198,7 +206,9 @@ export function useFlatToolRegistry(): ToolRegistry {
|
||||
view: "split",
|
||||
description: t("home.split.desc", "Split PDFs into multiple documents"),
|
||||
category: ToolCategory.STANDARD_TOOLS,
|
||||
subcategory: SubcategoryId.PAGE_FORMATTING
|
||||
subcategory: SubcategoryId.PAGE_FORMATTING,
|
||||
operationConfig: splitOperationConfig,
|
||||
settingsComponent: SplitSettings
|
||||
},
|
||||
"reorganize-pages": {
|
||||
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."),
|
||||
category: ToolCategory.RECOMMENDED_TOOLS,
|
||||
subcategory: SubcategoryId.GENERAL,
|
||||
maxFiles: -1
|
||||
maxFiles: -1,
|
||||
operationConfig: compressOperationConfig,
|
||||
settingsComponent: CompressSettings
|
||||
},
|
||||
"convert": {
|
||||
icon: <span className="material-symbols-rounded">sync_alt</span>,
|
||||
|
@ -1,13 +1,12 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useToolOperation } from '../shared/useToolOperation';
|
||||
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';
|
||||
|
||||
export const useAddPasswordOperation = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const buildFormData = (parameters: AddPasswordFullParameters, file: File): FormData => {
|
||||
// Static function that can be used by both the hook and automation executor
|
||||
export const buildAddPasswordFormData = (parameters: AddPasswordFullParameters, file: File): FormData => {
|
||||
const formData = new FormData();
|
||||
formData.append("fileInput", file);
|
||||
formData.append("password", parameters.password);
|
||||
@ -17,14 +16,30 @@ export const useAddPasswordOperation = () => {
|
||||
formData.append(key, value);
|
||||
});
|
||||
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',
|
||||
endpoint: '/api/v1/security/add-password',
|
||||
buildFormData,
|
||||
filePrefix: t('addPassword.filenamePrefix', 'encrypted') + '_',
|
||||
buildFormData: buildAddPasswordFormData,
|
||||
filePrefix: 'encrypted_', // Will be overridden in hook with translation
|
||||
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.'))
|
||||
});
|
||||
};
|
||||
|
@ -1,12 +1,18 @@
|
||||
import { useToolOperation } from '../shared/useToolOperation';
|
||||
import { useCallback } from 'react';
|
||||
import { executeAutomationSequence } from '../../../utils/automationExecutor';
|
||||
import { useFlatToolRegistry } from '../../../data/useTranslatedToolRegistry';
|
||||
|
||||
interface AutomateParameters {
|
||||
automationConfig?: any;
|
||||
onStepStart?: (stepIndex: number, operationName: string) => void;
|
||||
onStepComplete?: (stepIndex: number, resultFiles: File[]) => void;
|
||||
onStepError?: (stepIndex: number, error: string) => void;
|
||||
}
|
||||
|
||||
export function useAutomateOperation() {
|
||||
const toolRegistry = useFlatToolRegistry();
|
||||
|
||||
const customProcessor = useCallback(async (params: AutomateParameters, files: File[]) => {
|
||||
console.log('🚀 Starting automation execution via customProcessor', { params, files });
|
||||
|
||||
@ -18,21 +24,25 @@ export function useAutomateOperation() {
|
||||
const finalResults = await executeAutomationSequence(
|
||||
params.automationConfig,
|
||||
files,
|
||||
toolRegistry,
|
||||
(stepIndex: number, operationName: string) => {
|
||||
console.log(`Step ${stepIndex + 1} started: ${operationName}`);
|
||||
params.onStepStart?.(stepIndex, operationName);
|
||||
},
|
||||
(stepIndex: number, resultFiles: File[]) => {
|
||||
console.log(`Step ${stepIndex + 1} completed with ${resultFiles.length} files`);
|
||||
params.onStepComplete?.(stepIndex, resultFiles);
|
||||
},
|
||||
(stepIndex: number, error: string) => {
|
||||
console.error(`Step ${stepIndex + 1} failed:`, error);
|
||||
params.onStepError?.(stepIndex, error);
|
||||
throw new Error(`Automation step ${stepIndex + 1} failed: ${error}`);
|
||||
}
|
||||
);
|
||||
|
||||
console.log(`✅ Automation completed, returning ${finalResults.length} files`);
|
||||
return finalResults;
|
||||
}, []);
|
||||
}, [toolRegistry]);
|
||||
|
||||
return useToolOperation<AutomateParameters>({
|
||||
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 { useToolOperation } from '../shared/useToolOperation';
|
||||
import { useToolOperation, ToolOperationConfig } from '../shared/useToolOperation';
|
||||
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();
|
||||
formData.append("fileInput", file);
|
||||
|
||||
@ -21,15 +22,21 @@ const buildFormData = (parameters: CompressParameters, file: File): 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 = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return useToolOperation<CompressParameters>({
|
||||
operationType: 'compress',
|
||||
endpoint: '/api/v1/misc/compress-pdf',
|
||||
buildFormData,
|
||||
filePrefix: 'compressed_',
|
||||
multiFileEndpoint: false, // Individual API calls per file
|
||||
...compressOperationConfig,
|
||||
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';
|
||||
}
|
||||
|
||||
const defaultParameters: CompressParameters = {
|
||||
export const defaultParameters: CompressParameters = {
|
||||
compressionLevel: 5,
|
||||
grayscale: false,
|
||||
expectedSize: '',
|
||||
|
@ -61,6 +61,9 @@ export interface ToolOperationConfig<TParams = void> {
|
||||
|
||||
/** Extract user-friendly error messages from API errors */
|
||||
getErrorMessage?: (error: any) => string;
|
||||
|
||||
/** Default parameter values for automation */
|
||||
defaultParameters?: TParams;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useToolOperation } from '../shared/useToolOperation';
|
||||
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
|
||||
import { SplitParameters } from './useSplitParameters';
|
||||
import { SplitParameters, defaultParameters } from './useSplitParameters';
|
||||
import { SPLIT_MODES } from '../../../constants/splitConstants';
|
||||
|
||||
|
||||
const buildFormData = (parameters: SplitParameters, selectedFiles: File[]): FormData => {
|
||||
// Static functions that can be used by both the hook and automation executor
|
||||
export const buildSplitFormData = (parameters: SplitParameters, selectedFiles: File[]): FormData => {
|
||||
const formData = new FormData();
|
||||
|
||||
selectedFiles.forEach(file => {
|
||||
@ -40,7 +40,7 @@ const buildFormData = (parameters: SplitParameters, selectedFiles: File[]): Form
|
||||
return formData;
|
||||
};
|
||||
|
||||
const getEndpoint = (parameters: SplitParameters): string => {
|
||||
export const getSplitEndpoint = (parameters: SplitParameters): string => {
|
||||
switch (parameters.mode) {
|
||||
case SPLIT_MODES.BY_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 = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return useToolOperation<SplitParameters>({
|
||||
operationType: 'split',
|
||||
endpoint: (params) => getEndpoint(params),
|
||||
buildFormData: buildFormData, // Multi-file signature: (params, selectedFiles) => FormData
|
||||
filePrefix: 'split_',
|
||||
multiFileEndpoint: true, // Single API call with all files
|
||||
...splitOperationConfig,
|
||||
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>;
|
||||
|
||||
const defaultParameters: SplitParameters = {
|
||||
export const defaultParameters: SplitParameters = {
|
||||
mode: '',
|
||||
pages: '',
|
||||
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;
|
||||
|
@ -25,14 +25,28 @@ const Automate = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||
const automateOperation = useAutomateOperation();
|
||||
const toolRegistry = useFlatToolRegistry();
|
||||
const hasResults = automateOperation.files.length > 0 || automateOperation.downloadUrl !== null;
|
||||
const { savedAutomations, deleteAutomation } = useSavedAutomations();
|
||||
const { savedAutomations, deleteAutomation, refreshAutomations } = useSavedAutomations();
|
||||
|
||||
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);
|
||||
setCurrentStep(data.step);
|
||||
};
|
||||
|
||||
const handleComplete = () => {
|
||||
// Reset automation results when completing
|
||||
automateOperation.resetResults();
|
||||
|
||||
// Reset to selection step
|
||||
setCurrentStep('selection');
|
||||
setStepData({});
|
||||
@ -65,7 +79,10 @@ const Automate = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||
mode={stepData.mode}
|
||||
existingAutomation={stepData.automation}
|
||||
onBack={() => handleStepChange({ step: 'selection' })}
|
||||
onComplete={() => handleStepChange({ step: 'selection' })}
|
||||
onComplete={() => {
|
||||
refreshAutomations();
|
||||
handleStepChange({ step: 'selection' });
|
||||
}}
|
||||
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;
|
||||
|
@ -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;
|
||||
|
@ -1,76 +1,31 @@
|
||||
import axios from 'axios';
|
||||
|
||||
// Tool operation configurations extracted from the hook implementations
|
||||
const TOOL_CONFIGS: Record<string, any> = {
|
||||
'compressPdfs': {
|
||||
endpoint: '/api/v1/misc/compress-pdf',
|
||||
multiFileEndpoint: false,
|
||||
buildFormData: (parameters: any, file: File): FormData => {
|
||||
const formData = new FormData();
|
||||
formData.append("fileInput", file);
|
||||
|
||||
if (parameters.compressionMethod === 'quality') {
|
||||
formData.append("optimizeLevel", parameters.compressionLevel?.toString() || '1');
|
||||
} else {
|
||||
const fileSize = parameters.fileSizeValue ? `${parameters.fileSizeValue}${parameters.fileSizeUnit}` : '';
|
||||
if (fileSize) {
|
||||
formData.append("expectedOutputSize", fileSize);
|
||||
}
|
||||
}
|
||||
|
||||
formData.append("grayscale", parameters.grayscale?.toString() || 'false');
|
||||
return formData;
|
||||
}
|
||||
},
|
||||
|
||||
'split': {
|
||||
endpoint: (parameters: any): string => {
|
||||
// Simplified endpoint selection - you'd need the full logic from useSplitOperation
|
||||
return "/api/v1/general/split-pages";
|
||||
},
|
||||
multiFileEndpoint: true,
|
||||
buildFormData: (parameters: any, files: File[]): FormData => {
|
||||
const formData = new FormData();
|
||||
files.forEach(file => {
|
||||
formData.append("fileInput", file);
|
||||
});
|
||||
|
||||
// Add split parameters - simplified version
|
||||
if (parameters.pages) {
|
||||
formData.append("pageNumbers", parameters.pages);
|
||||
}
|
||||
|
||||
return formData;
|
||||
}
|
||||
},
|
||||
|
||||
'addPassword': {
|
||||
endpoint: '/api/v1/security/add-password',
|
||||
multiFileEndpoint: false,
|
||||
buildFormData: (parameters: any, file: File): FormData => {
|
||||
const formData = new FormData();
|
||||
formData.append("fileInput", file);
|
||||
|
||||
if (parameters.password) {
|
||||
formData.append("password", parameters.password);
|
||||
}
|
||||
|
||||
// Add other password parameters as needed
|
||||
return formData;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add configurations for other tools
|
||||
};
|
||||
import { ToolRegistry } from '../data/toolsTaxonomy';
|
||||
import { zipFileService } from '../services/zipFileService';
|
||||
|
||||
/**
|
||||
* Extract zip files from response blob
|
||||
*/
|
||||
const extractZipFiles = async (blob: Blob): Promise<File[]> => {
|
||||
// This would need the actual zip extraction logic from the codebase
|
||||
// For now, create a single file from the blob
|
||||
try {
|
||||
// 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' });
|
||||
return [file];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -79,11 +34,12 @@ const extractZipFiles = async (blob: Blob): Promise<File[]> => {
|
||||
export const executeToolOperation = async (
|
||||
operationName: string,
|
||||
parameters: any,
|
||||
files: File[]
|
||||
files: File[],
|
||||
toolRegistry: ToolRegistry
|
||||
): Promise<File[]> => {
|
||||
console.log(`🔧 Executing tool: ${operationName}`, { parameters, fileCount: files.length });
|
||||
|
||||
const config = TOOL_CONFIGS[operationName];
|
||||
const config = toolRegistry[operationName]?.operationConfig;
|
||||
if (!config) {
|
||||
console.error(`❌ Tool operation not supported: ${operationName}`);
|
||||
throw new Error(`Tool operation not supported: ${operationName}`);
|
||||
@ -99,7 +55,7 @@ export const executeToolOperation = async (
|
||||
: config.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()));
|
||||
|
||||
const response = await axios.post(endpoint, formData, {
|
||||
@ -126,7 +82,7 @@ export const executeToolOperation = async (
|
||||
: config.endpoint;
|
||||
|
||||
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()));
|
||||
|
||||
const response = await axios.post(endpoint, formData, {
|
||||
@ -162,6 +118,7 @@ export const executeToolOperation = async (
|
||||
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
|
||||
@ -189,7 +146,8 @@ export const executeAutomationSequence = async (
|
||||
const resultFiles = await executeToolOperation(
|
||||
operation.operation,
|
||||
operation.parameters || {},
|
||||
currentFiles
|
||||
currentFiles,
|
||||
toolRegistry
|
||||
);
|
||||
|
||||
console.log(`✅ Step ${i + 1} completed: ${resultFiles.length} result files`);
|
||||
|
Loading…
x
Reference in New Issue
Block a user