mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-09-18 01:19:24 +00:00
Initial working version of automation support
This commit is contained in:
parent
b9b8e6e4e1
commit
e929d7e349
@ -12,7 +12,7 @@ import {
|
|||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import CheckIcon from '@mui/icons-material/Check';
|
import CheckIcon from '@mui/icons-material/Check';
|
||||||
import { ToolRegistry } from '../../../data/toolsTaxonomy';
|
import { ToolRegistry } from '../../../data/toolsTaxonomy';
|
||||||
import ToolConfigurationModal from './ToolConfigurationModal';
|
import EnhancedToolConfigurationModal from './EnhancedToolConfigurationModal';
|
||||||
import ToolList from './ToolList';
|
import ToolList from './ToolList';
|
||||||
import IconSelector from './IconSelector';
|
import IconSelector from './IconSelector';
|
||||||
import { AutomationConfig, AutomationMode, AutomationTool } from '../../../types/automation';
|
import { AutomationConfig, AutomationMode, AutomationTool } from '../../../types/automation';
|
||||||
@ -216,7 +216,7 @@ export default function AutomationCreation({ mode, existingAutomation, onBack, o
|
|||||||
|
|
||||||
{/* Tool Configuration Modal */}
|
{/* Tool Configuration Modal */}
|
||||||
{currentConfigTool && (
|
{currentConfigTool && (
|
||||||
<ToolConfigurationModal
|
<EnhancedToolConfigurationModal
|
||||||
opened={configModalOpen}
|
opened={configModalOpen}
|
||||||
tool={currentConfigTool}
|
tool={currentConfigTool}
|
||||||
onSave={handleToolConfigSave}
|
onSave={handleToolConfigSave}
|
||||||
|
@ -0,0 +1,244 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Text,
|
||||||
|
Stack,
|
||||||
|
Group,
|
||||||
|
TextInput,
|
||||||
|
Textarea,
|
||||||
|
Divider,
|
||||||
|
Badge,
|
||||||
|
Alert
|
||||||
|
} from '@mantine/core';
|
||||||
|
import CheckIcon from '@mui/icons-material/Check';
|
||||||
|
import InfoIcon from '@mui/icons-material/Info';
|
||||||
|
import { ToolRegistry } from '../../../data/toolsTaxonomy';
|
||||||
|
import EnhancedToolConfigurationModal from './EnhancedToolConfigurationModal';
|
||||||
|
import ToolList from './ToolList';
|
||||||
|
import IconSelector from './IconSelector';
|
||||||
|
import { AutomationConfig, AutomationMode, AutomationTool } from '../../../types/automation';
|
||||||
|
import { useEnhancedAutomationForm } from '../../../hooks/tools/automate/useEnhancedAutomationForm';
|
||||||
|
|
||||||
|
interface AutomationCreationEnhancedProps {
|
||||||
|
mode: AutomationMode;
|
||||||
|
existingAutomation?: AutomationConfig;
|
||||||
|
onBack: () => void;
|
||||||
|
onComplete: (automation: AutomationConfig) => void;
|
||||||
|
toolRegistry: ToolRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enhanced automation creation component that works with both definition-based and legacy tools
|
||||||
|
*/
|
||||||
|
export default function AutomationCreationEnhanced({
|
||||||
|
mode,
|
||||||
|
existingAutomation,
|
||||||
|
onBack,
|
||||||
|
onComplete,
|
||||||
|
toolRegistry
|
||||||
|
}: AutomationCreationEnhancedProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const {
|
||||||
|
automationName,
|
||||||
|
setAutomationName,
|
||||||
|
automationDescription,
|
||||||
|
setAutomationDescription,
|
||||||
|
automationIcon,
|
||||||
|
setAutomationIcon,
|
||||||
|
selectedTools,
|
||||||
|
addTool,
|
||||||
|
removeTool,
|
||||||
|
updateTool,
|
||||||
|
hasUnsavedChanges,
|
||||||
|
canSaveAutomation,
|
||||||
|
getToolName,
|
||||||
|
getAutomatableTools,
|
||||||
|
isToolAutomatable
|
||||||
|
} = useEnhancedAutomationForm({ mode, existingAutomation, toolRegistry });
|
||||||
|
|
||||||
|
const [configModalOpen, setConfigModalOpen] = useState(false);
|
||||||
|
const [configTool, setConfigTool] = useState<AutomationTool | null>(null);
|
||||||
|
|
||||||
|
const automatableTools = getAutomatableTools();
|
||||||
|
const definitionBasedCount = automatableTools.filter(tool => tool.hasDefinition).length;
|
||||||
|
const legacyCount = automatableTools.filter(tool => tool.hasLegacySettings).length;
|
||||||
|
|
||||||
|
const handleToolConfig = (tool: AutomationTool) => {
|
||||||
|
setConfigTool(tool);
|
||||||
|
setConfigModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfigSave = (parameters: unknown) => {
|
||||||
|
if (configTool) {
|
||||||
|
updateTool(configTool.id, {
|
||||||
|
parameters: parameters as Record<string, unknown>,
|
||||||
|
configured: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setConfigModalOpen(false);
|
||||||
|
setConfigTool(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfigCancel = () => {
|
||||||
|
setConfigModalOpen(false);
|
||||||
|
setConfigTool(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
const automation: AutomationConfig = {
|
||||||
|
id: existingAutomation?.id || `automation-${Date.now()}`,
|
||||||
|
name: automationName,
|
||||||
|
description: automationDescription,
|
||||||
|
icon: automationIcon,
|
||||||
|
operations: selectedTools.map(tool => ({
|
||||||
|
operation: tool.operation,
|
||||||
|
parameters: tool.parameters
|
||||||
|
})),
|
||||||
|
createdAt: existingAutomation?.createdAt || new Date().toISOString(),
|
||||||
|
updatedAt: new Date().toISOString()
|
||||||
|
};
|
||||||
|
onComplete(automation);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Stack gap="lg">
|
||||||
|
{/* Info about tool types */}
|
||||||
|
<Alert icon={<InfoIcon />} color="blue">
|
||||||
|
<Text size="sm">
|
||||||
|
{t('automate.enhanced.info',
|
||||||
|
'Enhanced automation now supports {{definitionCount}} definition-based tools and {{legacyCount}} legacy tools.',
|
||||||
|
{ definitionCount: definitionBasedCount, legacyCount: legacyCount }
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</Alert>
|
||||||
|
|
||||||
|
{/* Automation Details */}
|
||||||
|
<Stack gap="md">
|
||||||
|
<Text size="lg" fw={600}>
|
||||||
|
{mode === AutomationMode.EDIT
|
||||||
|
? t('automate.edit.title', 'Edit Automation')
|
||||||
|
: t('automate.create.title', 'Create New Automation')
|
||||||
|
}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Group grow>
|
||||||
|
<TextInput
|
||||||
|
label={t('automate.name.label', 'Automation Name')}
|
||||||
|
placeholder={t('automate.name.placeholder', 'Enter automation name')}
|
||||||
|
value={automationName}
|
||||||
|
onChange={(e) => setAutomationName(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<IconSelector
|
||||||
|
value={automationIcon}
|
||||||
|
onChange={setAutomationIcon}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Textarea
|
||||||
|
label={t('automate.description.label', 'Description')}
|
||||||
|
placeholder={t('automate.description.placeholder', 'Describe what this automation does')}
|
||||||
|
value={automationDescription}
|
||||||
|
onChange={(e) => setAutomationDescription(e.target.value)}
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
{/* Tool Selection and Configuration */}
|
||||||
|
<Stack gap="md">
|
||||||
|
<Text size="md" fw={600}>
|
||||||
|
{t('automate.tools.title', 'Selected Tools')}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{selectedTools.length > 0 && (
|
||||||
|
<Stack gap="sm">
|
||||||
|
{selectedTools.map((tool, index) => {
|
||||||
|
const toolEntry = toolRegistry[tool.operation as keyof ToolRegistry];
|
||||||
|
const hasDefinition = !!toolEntry?.definition;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group key={tool.id} justify="space-between" p="sm" style={{ border: '1px solid #e0e0e0', borderRadius: '4px' }}>
|
||||||
|
<Group>
|
||||||
|
<Text fw={500}>{index + 1}.</Text>
|
||||||
|
<Text>{tool.name}</Text>
|
||||||
|
{hasDefinition && (
|
||||||
|
<Badge size="xs" color="blue">Definition-based</Badge>
|
||||||
|
)}
|
||||||
|
{!hasDefinition && toolEntry?.settingsComponent && (
|
||||||
|
<Badge size="xs" color="orange">Legacy</Badge>
|
||||||
|
)}
|
||||||
|
{tool.configured && (
|
||||||
|
<Badge size="xs" color="green">Configured</Badge>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
<Group gap="xs">
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => handleToolConfig(tool)}
|
||||||
|
>
|
||||||
|
{tool.configured
|
||||||
|
? t('automate.tool.reconfigure', 'Reconfigure')
|
||||||
|
: t('automate.tool.configure', 'Configure')
|
||||||
|
}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="outline"
|
||||||
|
color="red"
|
||||||
|
onClick={() => removeTool(tool.id)}
|
||||||
|
>
|
||||||
|
{t('common.remove', 'Remove')}
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ToolList
|
||||||
|
toolRegistry={toolRegistry}
|
||||||
|
onToolSelect={addTool}
|
||||||
|
selectedToolIds={selectedTools.map(t => t.operation)}
|
||||||
|
showOnlyAutomatable={true}
|
||||||
|
automatableToolsFilter={isToolAutomatable}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
{/* Action Buttons */}
|
||||||
|
<Group justify="flex-end" gap="sm">
|
||||||
|
<Button variant="outline" onClick={onBack}>
|
||||||
|
{t('common.back', 'Back')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
leftSection={<CheckIcon />}
|
||||||
|
onClick={handleSave}
|
||||||
|
disabled={!canSaveAutomation}
|
||||||
|
>
|
||||||
|
{mode === AutomationMode.EDIT
|
||||||
|
? t('automate.update', 'Update Automation')
|
||||||
|
: t('automate.create', 'Create Automation')
|
||||||
|
}
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
{/* Configuration Modal */}
|
||||||
|
{configModalOpen && configTool && (
|
||||||
|
<EnhancedToolConfigurationModal
|
||||||
|
opened={configModalOpen}
|
||||||
|
tool={configTool}
|
||||||
|
onSave={handleConfigSave}
|
||||||
|
onCancel={handleConfigCancel}
|
||||||
|
toolRegistry={toolRegistry}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Alert, Text, Stack } from '@mantine/core';
|
||||||
|
import WarningIcon from '@mui/icons-material/Warning';
|
||||||
|
import { ToolDefinition } from '../shared/toolDefinition';
|
||||||
|
|
||||||
|
interface DefinitionBasedToolConfigProps {
|
||||||
|
definition: ToolDefinition<unknown>;
|
||||||
|
parameters: Record<string, unknown>;
|
||||||
|
onParameterChange: (key: string, value: unknown) => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component that renders tool settings from a ToolDefinition
|
||||||
|
* This allows automation to work with definition-based tools automatically
|
||||||
|
*/
|
||||||
|
export default function DefinitionBasedToolConfig({
|
||||||
|
definition,
|
||||||
|
parameters,
|
||||||
|
onParameterChange,
|
||||||
|
disabled = false
|
||||||
|
}: DefinitionBasedToolConfigProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
// Get steps from definition (handle both static and dynamic)
|
||||||
|
const stepDefinitions = typeof definition.steps === 'function'
|
||||||
|
? definition.steps(parameters, true, false) // hasFiles = true, hasResults = false (automation context)
|
||||||
|
: definition.steps;
|
||||||
|
|
||||||
|
// Show all steps that aren't explicitly hidden
|
||||||
|
const visibleSteps = stepDefinitions.filter((stepDef) => {
|
||||||
|
return stepDef.isVisible !== false; // Show unless explicitly set to false (not a function)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (visibleSteps.length === 0) {
|
||||||
|
return (
|
||||||
|
<Alert icon={<WarningIcon />} color="orange">
|
||||||
|
<Text size="sm">
|
||||||
|
{t('automate.config.noSettings', 'This tool does not have configurable settings.')}
|
||||||
|
</Text>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack gap="md">
|
||||||
|
{visibleSteps.map((stepDef) => (
|
||||||
|
<div key={stepDef.key}>
|
||||||
|
<Text size="sm" fw={500} mb="xs">
|
||||||
|
{stepDef.title(t)}
|
||||||
|
</Text>
|
||||||
|
<stepDef.component
|
||||||
|
parameters={parameters}
|
||||||
|
onParameterChange={onParameterChange}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,176 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
Title,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
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';
|
||||||
|
import { ToolDefinition } from '../shared/toolDefinition';
|
||||||
|
import { getAvailableToExtensions } from '../../../utils/convertUtils';
|
||||||
|
import DefinitionBasedToolConfig from './DefinitionBasedToolConfig';
|
||||||
|
|
||||||
|
interface EnhancedToolConfigurationModalProps {
|
||||||
|
opened: boolean;
|
||||||
|
tool: {
|
||||||
|
id: string;
|
||||||
|
operation: string;
|
||||||
|
name: string;
|
||||||
|
parameters?: unknown;
|
||||||
|
};
|
||||||
|
onSave: (parameters: unknown) => void;
|
||||||
|
onCancel: () => void;
|
||||||
|
toolRegistry: ToolRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function EnhancedToolConfigurationModal({
|
||||||
|
opened,
|
||||||
|
tool,
|
||||||
|
onSave,
|
||||||
|
onCancel,
|
||||||
|
toolRegistry
|
||||||
|
}: EnhancedToolConfigurationModalProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const [legacyParameters, setLegacyParameters] = useState<Record<string, unknown>>({});
|
||||||
|
const [isValid, setIsValid] = useState(true);
|
||||||
|
|
||||||
|
// Get tool info from registry
|
||||||
|
const toolInfo = toolRegistry[tool.operation as keyof ToolRegistry];
|
||||||
|
const hasDefinition = !!toolInfo?.definition;
|
||||||
|
const SettingsComponent = toolInfo?.settingsComponent;
|
||||||
|
|
||||||
|
// For definition-based tools, use the actual hook
|
||||||
|
const definitionParams = hasDefinition ? (toolInfo.definition as ToolDefinition<unknown>).useParameters() : null;
|
||||||
|
|
||||||
|
// Initialize legacy parameters
|
||||||
|
useEffect(() => {
|
||||||
|
if (!hasDefinition) {
|
||||||
|
if (tool.parameters) {
|
||||||
|
setLegacyParameters(tool.parameters as Record<string, unknown>);
|
||||||
|
} else {
|
||||||
|
setLegacyParameters({});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [tool.parameters, tool.operation, hasDefinition]);
|
||||||
|
|
||||||
|
// Handle parameter changes
|
||||||
|
const handleParameterChange = (key: string, value: unknown) => {
|
||||||
|
if (hasDefinition && definitionParams) {
|
||||||
|
definitionParams.updateParameter(key, value);
|
||||||
|
} else {
|
||||||
|
setLegacyParameters((prev) => ({ ...prev, [key]: value }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const parameters = hasDefinition && definitionParams ? definitionParams.parameters : legacyParameters;
|
||||||
|
|
||||||
|
// Render the settings component
|
||||||
|
const renderToolSettings = () => {
|
||||||
|
if (hasDefinition) {
|
||||||
|
// Use definition-based rendering
|
||||||
|
const definition = toolInfo.definition as ToolDefinition<unknown>;
|
||||||
|
return (
|
||||||
|
<DefinitionBasedToolConfig
|
||||||
|
definition={definition}
|
||||||
|
parameters={parameters}
|
||||||
|
onParameterChange={handleParameterChange}
|
||||||
|
disabled={false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SettingsComponent) {
|
||||||
|
return (
|
||||||
|
<Alert icon={<WarningIcon />} color="orange">
|
||||||
|
<Text size="sm">
|
||||||
|
{t('automate.config.noSettings', 'This tool does not have configurable settings.')}
|
||||||
|
</Text>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy settings component rendering
|
||||||
|
if (tool.operation === 'convert') {
|
||||||
|
return (
|
||||||
|
<SettingsComponent
|
||||||
|
parameters={parameters}
|
||||||
|
onParameterChange={handleParameterChange}
|
||||||
|
getAvailableToExtensions={getAvailableToExtensions}
|
||||||
|
selectedFiles={[]}
|
||||||
|
disabled={false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingsComponent
|
||||||
|
parameters={parameters}
|
||||||
|
onParameterChange={handleParameterChange}
|
||||||
|
disabled={false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
if (isValid) {
|
||||||
|
const finalParameters = hasDefinition && definitionParams ? definitionParams.parameters : legacyParameters;
|
||||||
|
onSave(finalParameters);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
opened={opened}
|
||||||
|
onClose={onCancel}
|
||||||
|
title={
|
||||||
|
<Group gap="xs">
|
||||||
|
<SettingsIcon />
|
||||||
|
<Title order={4}>
|
||||||
|
{t('automate.config.title', 'Configure {{toolName}}', { toolName: tool.name })}
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
}
|
||||||
|
size="lg"
|
||||||
|
scrollAreaComponent="div"
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
{hasDefinition
|
||||||
|
? t('automate.config.descriptionDefinition', 'Configure settings for this tool. These settings will be applied when the automation runs.')
|
||||||
|
: t('automate.config.descriptionLegacy', 'Configure settings for this tool using the legacy interface.')
|
||||||
|
}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<div style={{ minHeight: '200px' }}>
|
||||||
|
{renderToolSettings()}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Group justify="flex-end" gap="sm">
|
||||||
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
leftSection={<CloseIcon />}
|
||||||
|
onClick={onCancel}
|
||||||
|
>
|
||||||
|
{t('common.cancel', 'Cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
leftSection={<CheckIcon />}
|
||||||
|
onClick={handleSave}
|
||||||
|
disabled={!isValid}
|
||||||
|
>
|
||||||
|
{t('common.save', 'Save')}
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
@ -4,6 +4,7 @@ import { ToolOperationConfig } from '../hooks/tools/shared/useToolOperation';
|
|||||||
import { BaseToolProps } from '../types/tool';
|
import { BaseToolProps } from '../types/tool';
|
||||||
import { WorkbenchType } from '../types/workbench';
|
import { WorkbenchType } from '../types/workbench';
|
||||||
import { ToolId } from '../types/toolId';
|
import { ToolId } from '../types/toolId';
|
||||||
|
import { ToolDefinition } from '../components/tools/shared/toolDefinition';
|
||||||
|
|
||||||
export enum SubcategoryId {
|
export enum SubcategoryId {
|
||||||
SIGNING = 'signing',
|
SIGNING = 'signing',
|
||||||
@ -41,6 +42,8 @@ export type ToolRegistryEntry = {
|
|||||||
urlPath?: string;
|
urlPath?: string;
|
||||||
// Workbench type for navigation
|
// Workbench type for navigation
|
||||||
workbench?: WorkbenchType;
|
workbench?: WorkbenchType;
|
||||||
|
// Tool definition for definition-based tools (cast to specific type at point of use)
|
||||||
|
definition?: ToolDefinition<unknown>;
|
||||||
// Operation configuration for automation
|
// Operation configuration for automation
|
||||||
operationConfig?: ToolOperationConfig<any>;
|
operationConfig?: ToolOperationConfig<any>;
|
||||||
// Settings component for automation configuration
|
// Settings component for automation configuration
|
||||||
|
@ -9,37 +9,31 @@ import Sanitize from "../tools/Sanitize";
|
|||||||
import AddPassword from "../tools/AddPassword";
|
import AddPassword from "../tools/AddPassword";
|
||||||
import ChangePermissions from "../tools/ChangePermissions";
|
import ChangePermissions from "../tools/ChangePermissions";
|
||||||
import RemovePassword from "../tools/RemovePassword";
|
import RemovePassword from "../tools/RemovePassword";
|
||||||
import { SubcategoryId, ToolCategoryId, ToolRegistry } from "./toolsTaxonomy";
|
import { SubcategoryId, ToolCategoryId, ToolRegistry, ToolRegistryEntry } from "./toolsTaxonomy";
|
||||||
import AddWatermark from "../tools/AddWatermark";
|
import AddWatermark from "../tools/AddWatermark";
|
||||||
import Repair from "../tools/Repair";
|
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";
|
// Tool definitions
|
||||||
import { splitOperationConfig } from "../hooks/tools/split/useSplitOperation";
|
import { compressDefinition } from "../tools/definitions/compressDefinition";
|
||||||
|
import { splitDefinition } from "../tools/definitions/splitDefinition";
|
||||||
|
import { addWatermarkDefinition } from "../tools/definitions/addWatermarkDefinition";
|
||||||
|
import { repairDefinition } from "../tools/definitions/repairDefinition";
|
||||||
|
import { sanitizeDefinition } from "../tools/definitions/sanitizeDefinition";
|
||||||
|
import { removePasswordDefinition } from "../tools/definitions/removePasswordDefinition";
|
||||||
|
import { unlockPdfFormsDefinition } from "../tools/definitions/unlockPdfFormsDefinition";
|
||||||
|
import { singleLargePageDefinition } from "../tools/definitions/singleLargePageDefinition";
|
||||||
|
import { removeCertificateSignDefinition } from "../tools/definitions/removeCertificateSignDefinition";
|
||||||
|
import { changePermissionsDefinition } from "../tools/definitions/changePermissionsDefinition";
|
||||||
import { addPasswordOperationConfig } from "../hooks/tools/addPassword/useAddPasswordOperation";
|
import { addPasswordOperationConfig } from "../hooks/tools/addPassword/useAddPasswordOperation";
|
||||||
import { removePasswordOperationConfig } from "../hooks/tools/removePassword/useRemovePasswordOperation";
|
|
||||||
import { sanitizeOperationConfig } from "../hooks/tools/sanitize/useSanitizeOperation";
|
|
||||||
import { repairOperationConfig } from "../hooks/tools/repair/useRepairOperation";
|
|
||||||
import { addWatermarkOperationConfig } from "../hooks/tools/addWatermark/useAddWatermarkOperation";
|
|
||||||
import { unlockPdfFormsOperationConfig } from "../hooks/tools/unlockPdfForms/useUnlockPdfFormsOperation";
|
|
||||||
import { singleLargePageOperationConfig } from "../hooks/tools/singleLargePage/useSingleLargePageOperation";
|
|
||||||
import { ocrOperationConfig } from "../hooks/tools/ocr/useOCROperation";
|
import { ocrOperationConfig } from "../hooks/tools/ocr/useOCROperation";
|
||||||
import { convertOperationConfig } from "../hooks/tools/convert/useConvertOperation";
|
import { convertOperationConfig } from "../hooks/tools/convert/useConvertOperation";
|
||||||
import { removeCertificateSignOperationConfig } from "../hooks/tools/removeCertificateSign/useRemoveCertificateSignOperation";
|
|
||||||
import { changePermissionsOperationConfig } from "../hooks/tools/changePermissions/useChangePermissionsOperation";
|
|
||||||
import CompressSettings from "../components/tools/compress/CompressSettings";
|
|
||||||
import SplitSettings from "../components/tools/split/SplitSettings";
|
|
||||||
import AddPasswordSettings from "../components/tools/addPassword/AddPasswordSettings";
|
import AddPasswordSettings from "../components/tools/addPassword/AddPasswordSettings";
|
||||||
import RemovePasswordSettings from "../components/tools/removePassword/RemovePasswordSettings";
|
|
||||||
import SanitizeSettings from "../components/tools/sanitize/SanitizeSettings";
|
|
||||||
import RepairSettings from "../components/tools/repair/RepairSettings";
|
|
||||||
import UnlockPdfFormsSettings from "../components/tools/unlockPdfForms/UnlockPdfFormsSettings";
|
|
||||||
import AddWatermarkSingleStepSettings from "../components/tools/addWatermark/AddWatermarkSingleStepSettings";
|
|
||||||
import OCRSettings from "../components/tools/ocr/OCRSettings";
|
import OCRSettings from "../components/tools/ocr/OCRSettings";
|
||||||
import ConvertSettings from "../components/tools/convert/ConvertSettings";
|
import ConvertSettings from "../components/tools/convert/ConvertSettings";
|
||||||
import ChangePermissionsSettings from "../components/tools/changePermissions/ChangePermissionsSettings";
|
|
||||||
import { ToolId } from "../types/toolId";
|
import { ToolId } from "../types/toolId";
|
||||||
|
import { ToolDefinition } from "../components/tools/shared/toolDefinition";
|
||||||
|
|
||||||
const showPlaceholderTools = true; // Show all tools; grey out unavailable ones in UI
|
const showPlaceholderTools = true; // Show all tools; grey out unavailable ones in UI
|
||||||
|
|
||||||
@ -167,13 +161,12 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
icon: <LocalIcon icon="branding-watermark-outline-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="branding-watermark-outline-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.watermark.title", "Add Watermark"),
|
name: t("home.watermark.title", "Add Watermark"),
|
||||||
component: AddWatermark,
|
component: AddWatermark,
|
||||||
|
definition: addWatermarkDefinition as ToolDefinition<unknown>, // Somewhat ugly hack for the sake of prototyping
|
||||||
maxFiles: -1,
|
maxFiles: -1,
|
||||||
description: t("home.watermark.desc", "Add a custom watermark to your PDF document."),
|
description: t("home.watermark.desc", "Add a custom watermark to your PDF document."),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
|
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
|
||||||
endpoints: ["add-watermark"],
|
endpoints: ["add-watermark"],
|
||||||
operationConfig: addWatermarkOperationConfig,
|
|
||||||
settingsComponent: AddWatermarkSingleStepSettings,
|
|
||||||
},
|
},
|
||||||
"add-stamp": {
|
"add-stamp": {
|
||||||
icon: <LocalIcon icon="approval-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="approval-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -187,13 +180,12 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
icon: <LocalIcon icon="cleaning-services-outline-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="cleaning-services-outline-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.sanitize.title", "Sanitize"),
|
name: t("home.sanitize.title", "Sanitize"),
|
||||||
component: Sanitize,
|
component: Sanitize,
|
||||||
|
definition: sanitizeDefinition as ToolDefinition<unknown>, // Somewhat ugly hack for the sake of prototyping
|
||||||
maxFiles: -1,
|
maxFiles: -1,
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
|
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
|
||||||
description: t("home.sanitize.desc", "Remove potentially harmful elements from PDF files"),
|
description: t("home.sanitize.desc", "Remove potentially harmful elements from PDF files"),
|
||||||
endpoints: ["sanitize-pdf"],
|
endpoints: ["sanitize-pdf"],
|
||||||
operationConfig: sanitizeOperationConfig,
|
|
||||||
settingsComponent: SanitizeSettings,
|
|
||||||
},
|
},
|
||||||
flatten: {
|
flatten: {
|
||||||
icon: <LocalIcon icon="layers-clear-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="layers-clear-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -207,13 +199,12 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
icon: <LocalIcon icon="preview-off-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="preview-off-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.unlockPDFForms.title", "Unlock PDF Forms"),
|
name: t("home.unlockPDFForms.title", "Unlock PDF Forms"),
|
||||||
component: UnlockPdfForms,
|
component: UnlockPdfForms,
|
||||||
|
definition: unlockPdfFormsDefinition as ToolDefinition<unknown>, // Somewhat ugly hack for the sake of prototyping
|
||||||
description: t("home.unlockPDFForms.desc", "Remove read-only property of form fields in a PDF document."),
|
description: t("home.unlockPDFForms.desc", "Remove read-only property of form fields in a PDF document."),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
|
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
|
||||||
maxFiles: -1,
|
maxFiles: -1,
|
||||||
endpoints: ["unlock-pdf-forms"],
|
endpoints: ["unlock-pdf-forms"],
|
||||||
operationConfig: unlockPdfFormsOperationConfig,
|
|
||||||
settingsComponent: UnlockPdfFormsSettings,
|
|
||||||
},
|
},
|
||||||
"manage-certificates": {
|
"manage-certificates": {
|
||||||
icon: <LocalIcon icon="license-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="license-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -230,13 +221,12 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
icon: <LocalIcon icon="lock-outline" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="lock-outline" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.changePermissions.title", "Change Permissions"),
|
name: t("home.changePermissions.title", "Change Permissions"),
|
||||||
component: ChangePermissions,
|
component: ChangePermissions,
|
||||||
|
definition: changePermissionsDefinition as ToolDefinition<unknown>, // Somewhat ugly hack for the sake of prototyping
|
||||||
description: t("home.changePermissions.desc", "Change document restrictions and permissions"),
|
description: t("home.changePermissions.desc", "Change document restrictions and permissions"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
|
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
|
||||||
maxFiles: -1,
|
maxFiles: -1,
|
||||||
endpoints: ["add-password"],
|
endpoints: ["add-password"],
|
||||||
operationConfig: changePermissionsOperationConfig,
|
|
||||||
settingsComponent: ChangePermissionsSettings,
|
|
||||||
},
|
},
|
||||||
// Verification
|
// Verification
|
||||||
|
|
||||||
@ -301,11 +291,10 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
icon: <LocalIcon icon="content-cut-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="content-cut-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.split.title", "Split"),
|
name: t("home.split.title", "Split"),
|
||||||
component: SplitPdfPanel,
|
component: SplitPdfPanel,
|
||||||
|
definition: splitDefinition as ToolDefinition<unknown>, // Somewhat ugly hack for the sake of prototyping
|
||||||
description: t("home.split.desc", "Split PDFs into multiple documents"),
|
description: t("home.split.desc", "Split PDFs into multiple documents"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.PAGE_FORMATTING,
|
subcategoryId: SubcategoryId.PAGE_FORMATTING,
|
||||||
operationConfig: splitOperationConfig,
|
|
||||||
settingsComponent: SplitSettings,
|
|
||||||
},
|
},
|
||||||
"reorganize-pages": {
|
"reorganize-pages": {
|
||||||
icon: <LocalIcon icon="move-down-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="move-down-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -350,13 +339,12 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
icon: <LocalIcon icon="looks-one-outline-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="looks-one-outline-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.pdfToSinglePage.title", "PDF to Single Large Page"),
|
name: t("home.pdfToSinglePage.title", "PDF to Single Large Page"),
|
||||||
component: SingleLargePage,
|
component: SingleLargePage,
|
||||||
|
definition: singleLargePageDefinition as ToolDefinition<unknown>, // Somewhat ugly hack for the sake of prototyping
|
||||||
description: t("home.pdfToSinglePage.desc", "Merges all PDF pages into one large single page"),
|
description: t("home.pdfToSinglePage.desc", "Merges all PDF pages into one large single page"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.PAGE_FORMATTING,
|
subcategoryId: SubcategoryId.PAGE_FORMATTING,
|
||||||
maxFiles: -1,
|
maxFiles: -1,
|
||||||
endpoints: ["pdf-to-single-page"],
|
endpoints: ["pdf-to-single-page"],
|
||||||
operationConfig: singleLargePageOperationConfig,
|
|
||||||
},
|
},
|
||||||
"add-attachments": {
|
"add-attachments": {
|
||||||
icon: <LocalIcon icon="attachment-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="attachment-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -425,24 +413,23 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
icon: <LocalIcon icon="lock-open-right-outline-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="lock-open-right-outline-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.removePassword.title", "Remove Password"),
|
name: t("home.removePassword.title", "Remove Password"),
|
||||||
component: RemovePassword,
|
component: RemovePassword,
|
||||||
|
definition: removePasswordDefinition as ToolDefinition<unknown>, // Somewhat ugly hack for the sake of prototyping
|
||||||
description: t("home.removePassword.desc", "Remove password protection from PDF documents"),
|
description: t("home.removePassword.desc", "Remove password protection from PDF documents"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.REMOVAL,
|
subcategoryId: SubcategoryId.REMOVAL,
|
||||||
endpoints: ["remove-password"],
|
endpoints: ["remove-password"],
|
||||||
maxFiles: -1,
|
maxFiles: -1,
|
||||||
operationConfig: removePasswordOperationConfig,
|
|
||||||
settingsComponent: RemovePasswordSettings,
|
|
||||||
},
|
},
|
||||||
"remove-certificate-sign": {
|
"remove-certificate-sign": {
|
||||||
icon: <LocalIcon icon="remove-moderator-outline-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="remove-moderator-outline-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.removeCertSign.title", "Remove Certificate Sign"),
|
name: t("home.removeCertSign.title", "Remove Certificate Sign"),
|
||||||
component: RemoveCertificateSign,
|
component: RemoveCertificateSign,
|
||||||
|
definition: removeCertificateSignDefinition as ToolDefinition<unknown>, // Somewhat ugly hack for the sake of prototyping
|
||||||
description: t("home.removeCertSign.desc", "Remove digital signature from PDF documents"),
|
description: t("home.removeCertSign.desc", "Remove digital signature from PDF documents"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.REMOVAL,
|
subcategoryId: SubcategoryId.REMOVAL,
|
||||||
maxFiles: -1,
|
maxFiles: -1,
|
||||||
endpoints: ["remove-certificate-sign"],
|
endpoints: ["remove-certificate-sign"],
|
||||||
operationConfig: removeCertificateSignOperationConfig,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Automation
|
// Automation
|
||||||
@ -500,13 +487,12 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
icon: <LocalIcon icon="build-outline-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="build-outline-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.repair.title", "Repair"),
|
name: t("home.repair.title", "Repair"),
|
||||||
component: Repair,
|
component: Repair,
|
||||||
|
definition: repairDefinition as ToolDefinition<unknown>, // Somewhat ugly hack for the sake of prototyping
|
||||||
description: t("home.repair.desc", "Repair corrupted or damaged PDF files"),
|
description: t("home.repair.desc", "Repair corrupted or damaged PDF files"),
|
||||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
|
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
|
||||||
maxFiles: -1,
|
maxFiles: -1,
|
||||||
endpoints: ["repair"],
|
endpoints: ["repair"],
|
||||||
operationConfig: repairOperationConfig,
|
|
||||||
settingsComponent: RepairSettings,
|
|
||||||
},
|
},
|
||||||
"detect-split-scanned-photos": {
|
"detect-split-scanned-photos": {
|
||||||
icon: <LocalIcon icon="scanner-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="scanner-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -617,12 +603,11 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
icon: <LocalIcon icon="zoom-in-map-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="zoom-in-map-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.compress.title", "Compress"),
|
name: t("home.compress.title", "Compress"),
|
||||||
component: CompressPdfPanel,
|
component: CompressPdfPanel,
|
||||||
|
definition: compressDefinition as ToolDefinition<unknown>, // Somewhat ugly hack for the sake of prototyping
|
||||||
description: t("home.compress.desc", "Compress PDFs to reduce their file size."),
|
description: t("home.compress.desc", "Compress PDFs to reduce their file size."),
|
||||||
categoryId: ToolCategoryId.RECOMMENDED_TOOLS,
|
categoryId: ToolCategoryId.RECOMMENDED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.GENERAL,
|
subcategoryId: SubcategoryId.GENERAL,
|
||||||
maxFiles: -1,
|
maxFiles: -1,
|
||||||
operationConfig: compressOperationConfig,
|
|
||||||
settingsComponent: CompressSettings,
|
|
||||||
},
|
},
|
||||||
convert: {
|
convert: {
|
||||||
icon: <LocalIcon icon="sync-alt-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="sync-alt-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
|
@ -15,9 +15,11 @@ export function useAutomateOperation() {
|
|||||||
throw new Error('No automation configuration provided');
|
throw new Error('No automation configuration provided');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute the automation sequence and return the final results
|
console.log('🔍 Full automation config:', params.automationConfig);
|
||||||
|
|
||||||
|
// Execute the automation sequence using the regular executor
|
||||||
const finalResults = await executeAutomationSequence(
|
const finalResults = await executeAutomationSequence(
|
||||||
params.automationConfig!,
|
params.automationConfig,
|
||||||
files,
|
files,
|
||||||
toolRegistry,
|
toolRegistry,
|
||||||
(stepIndex: number, operationName: string) => {
|
(stepIndex: number, operationName: string) => {
|
||||||
@ -31,7 +33,6 @@ export function useAutomateOperation() {
|
|||||||
(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);
|
params.onStepError?.(stepIndex, error);
|
||||||
throw new Error(`Automation step ${stepIndex + 1} failed: ${error}`);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
79
frontend/src/hooks/tools/automate/useAutomationExecutor.ts
Normal file
79
frontend/src/hooks/tools/automate/useAutomationExecutor.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
import { ToolRegistry } from '../../../data/toolsTaxonomy';
|
||||||
|
import { AutomationTool } from '../../../types/automation';
|
||||||
|
import { ToolDefinition } from '../../../components/tools/shared/toolDefinition';
|
||||||
|
|
||||||
|
export function useAutomationExecutor(toolRegistry: ToolRegistry) {
|
||||||
|
const executeStep = useCallback(async (tool: AutomationTool, files: File[]): Promise<File[]> => {
|
||||||
|
const toolEntry = toolRegistry[tool.operation as keyof ToolRegistry];
|
||||||
|
if (!toolEntry) {
|
||||||
|
throw new Error(`Tool ${tool.operation} not found in registry`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle definition-based tools
|
||||||
|
if (toolEntry.definition) {
|
||||||
|
const definition = toolEntry.definition as ToolDefinition<unknown>;
|
||||||
|
console.log(`🎯 Using definition-based tool: ${definition.id}`);
|
||||||
|
|
||||||
|
const operation = definition.useOperation();
|
||||||
|
const result = await operation.executeOperation(tool.parameters, files);
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Operation failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`✅ Definition-based tool returned ${result.files.length} files`);
|
||||||
|
return result.files;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle legacy tools with operationConfig
|
||||||
|
if (toolEntry.operationConfig) {
|
||||||
|
// Import the legacy executor function and use it
|
||||||
|
const { executeToolOperationWithPrefix } = await import('../../../utils/automationExecutor');
|
||||||
|
return executeToolOperationWithPrefix(
|
||||||
|
tool.operation,
|
||||||
|
tool.parameters || {},
|
||||||
|
files,
|
||||||
|
toolRegistry
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Tool ${tool.operation} has no execution method available`);
|
||||||
|
}, [toolRegistry]);
|
||||||
|
|
||||||
|
const executeSequence = useCallback(async (
|
||||||
|
tools: AutomationTool[],
|
||||||
|
initialFiles: File[],
|
||||||
|
onStepStart?: (stepIndex: number, operationName: string) => void,
|
||||||
|
onStepComplete?: (stepIndex: number, resultFiles: File[]) => void,
|
||||||
|
onStepError?: (stepIndex: number, error: string) => void
|
||||||
|
): Promise<File[]> => {
|
||||||
|
let currentFiles = initialFiles;
|
||||||
|
|
||||||
|
for (let i = 0; i < tools.length; i++) {
|
||||||
|
const tool = tools[i];
|
||||||
|
try {
|
||||||
|
onStepStart?.(i, tool.operation);
|
||||||
|
console.log(`🔄 Executing step ${i + 1}/${tools.length}: ${tool.operation}`);
|
||||||
|
|
||||||
|
const resultFiles = await executeStep(tool, currentFiles);
|
||||||
|
currentFiles = resultFiles;
|
||||||
|
|
||||||
|
onStepComplete?.(i, resultFiles);
|
||||||
|
console.log(`✅ Step ${i + 1} completed with ${resultFiles.length} files`);
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||||
|
console.error(`❌ Step ${i + 1} failed:`, error);
|
||||||
|
onStepError?.(i, errorMessage);
|
||||||
|
throw new Error(`Step ${i + 1} failed: ${errorMessage}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentFiles;
|
||||||
|
}, [executeStep]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
executeStep,
|
||||||
|
executeSequence
|
||||||
|
};
|
||||||
|
}
|
159
frontend/src/hooks/tools/automate/useEnhancedAutomationForm.ts
Normal file
159
frontend/src/hooks/tools/automate/useEnhancedAutomationForm.ts
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { AutomationTool, AutomationConfig, AutomationMode } from '../../../types/automation';
|
||||||
|
import { AUTOMATION_CONSTANTS } from '../../../constants/automation';
|
||||||
|
import { ToolRegistry } from '../../../data/toolsTaxonomy';
|
||||||
|
import { ToolDefinition } from '../../../components/tools/shared/toolDefinition';
|
||||||
|
|
||||||
|
interface UseEnhancedAutomationFormProps {
|
||||||
|
mode: AutomationMode;
|
||||||
|
existingAutomation?: AutomationConfig;
|
||||||
|
toolRegistry: ToolRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enhanced automation form hook that works with both definition-based and legacy tools
|
||||||
|
*/
|
||||||
|
export function useEnhancedAutomationForm({ mode, existingAutomation, toolRegistry }: UseEnhancedAutomationFormProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const [automationName, setAutomationName] = useState('');
|
||||||
|
const [automationDescription, setAutomationDescription] = useState('');
|
||||||
|
const [automationIcon, setAutomationIcon] = useState<string>('');
|
||||||
|
const [selectedTools, setSelectedTools] = useState<AutomationTool[]>([]);
|
||||||
|
|
||||||
|
const getToolName = useCallback((operation: string) => {
|
||||||
|
const tool = toolRegistry?.[operation as keyof ToolRegistry] as any;
|
||||||
|
return tool?.name || t(`tools.${operation}.name`, operation);
|
||||||
|
}, [toolRegistry, t]);
|
||||||
|
|
||||||
|
const getToolDefaultParameters = useCallback((operation: string): Record<string, unknown> => {
|
||||||
|
const toolEntry = toolRegistry[operation as keyof ToolRegistry];
|
||||||
|
if (!toolEntry) return {};
|
||||||
|
|
||||||
|
// Check if it's a definition-based tool
|
||||||
|
if (toolEntry.definition) {
|
||||||
|
const definition = toolEntry.definition as ToolDefinition<unknown>;
|
||||||
|
|
||||||
|
// For definition-based tools, we need to get defaults from the parameters hook
|
||||||
|
// This is tricky because we can't call hooks here, but we can provide sensible defaults
|
||||||
|
// TODO: Consider creating a static defaultParameters method on definitions
|
||||||
|
|
||||||
|
// For now, return empty object - the definition components should handle their own defaults
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy operationConfig approach
|
||||||
|
const config = toolEntry.operationConfig;
|
||||||
|
if (config?.defaultParameters) {
|
||||||
|
return { ...config.defaultParameters };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}, [toolRegistry]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get list of automatable tools from the registry
|
||||||
|
* Includes both definition-based and legacy tools with settingsComponent
|
||||||
|
*/
|
||||||
|
const getAutomatableTools = useCallback(() => {
|
||||||
|
return Object.entries(toolRegistry)
|
||||||
|
.filter(([_, toolEntry]) => {
|
||||||
|
// Include definition-based tools OR legacy tools with settings
|
||||||
|
return toolEntry.definition || toolEntry.settingsComponent;
|
||||||
|
})
|
||||||
|
.map(([toolId, toolEntry]) => ({
|
||||||
|
id: toolId,
|
||||||
|
name: toolEntry.name,
|
||||||
|
hasDefinition: !!toolEntry.definition,
|
||||||
|
hasLegacySettings: !!toolEntry.settingsComponent
|
||||||
|
}));
|
||||||
|
}, [toolRegistry]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a tool supports automation
|
||||||
|
*/
|
||||||
|
const isToolAutomatable = useCallback((operation: string) => {
|
||||||
|
const toolEntry = toolRegistry[operation as keyof ToolRegistry];
|
||||||
|
return !!(toolEntry?.definition || toolEntry?.settingsComponent);
|
||||||
|
}, [toolRegistry]);
|
||||||
|
|
||||||
|
// Initialize based on mode and existing automation
|
||||||
|
useEffect(() => {
|
||||||
|
if ((mode === AutomationMode.SUGGESTED || mode === AutomationMode.EDIT) && existingAutomation) {
|
||||||
|
setAutomationName(existingAutomation.name || '');
|
||||||
|
setAutomationDescription(existingAutomation.description || '');
|
||||||
|
setAutomationIcon(existingAutomation.icon || '');
|
||||||
|
|
||||||
|
const operations = existingAutomation.operations || [];
|
||||||
|
const tools = operations.map((op, index) => {
|
||||||
|
const operation = typeof op === 'string' ? op : op.operation;
|
||||||
|
return {
|
||||||
|
id: `${operation}-${Date.now()}-${index}`,
|
||||||
|
operation: operation,
|
||||||
|
name: getToolName(operation),
|
||||||
|
configured: mode === AutomationMode.EDIT ? true : false,
|
||||||
|
parameters: typeof op === 'object' ? op.parameters || {} : {}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
setSelectedTools(tools);
|
||||||
|
} else {
|
||||||
|
// Creating new automation
|
||||||
|
setAutomationName('');
|
||||||
|
setAutomationDescription('');
|
||||||
|
setAutomationIcon('');
|
||||||
|
setSelectedTools([]);
|
||||||
|
}
|
||||||
|
}, [mode, existingAutomation, getToolName]);
|
||||||
|
|
||||||
|
const addTool = useCallback((operation: string) => {
|
||||||
|
if (!isToolAutomatable(operation)) {
|
||||||
|
console.warn(`Tool ${operation} is not automatable`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newTool: AutomationTool = {
|
||||||
|
id: `${operation}-${Date.now()}`,
|
||||||
|
operation,
|
||||||
|
name: getToolName(operation),
|
||||||
|
configured: false,
|
||||||
|
parameters: getToolDefaultParameters(operation)
|
||||||
|
};
|
||||||
|
setSelectedTools(prev => [...prev, newTool]);
|
||||||
|
}, [getToolName, getToolDefaultParameters, isToolAutomatable]);
|
||||||
|
|
||||||
|
const removeTool = useCallback((toolId: string) => {
|
||||||
|
setSelectedTools(prev => prev.filter(tool => tool.id !== toolId));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const updateTool = useCallback((toolId: string, updates: Partial<AutomationTool>) => {
|
||||||
|
setSelectedTools(prev => prev.map(tool =>
|
||||||
|
tool.id === toolId ? { ...tool, ...updates } : tool
|
||||||
|
));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const hasUnsavedChanges = selectedTools.length > 0 || automationName.trim() !== '';
|
||||||
|
|
||||||
|
const canSaveAutomation = automationName.trim() !== '' &&
|
||||||
|
selectedTools.length > 0 &&
|
||||||
|
selectedTools.every(tool => tool.configured);
|
||||||
|
|
||||||
|
return {
|
||||||
|
automationName,
|
||||||
|
setAutomationName,
|
||||||
|
automationDescription,
|
||||||
|
setAutomationDescription,
|
||||||
|
automationIcon,
|
||||||
|
setAutomationIcon,
|
||||||
|
selectedTools,
|
||||||
|
addTool,
|
||||||
|
removeTool,
|
||||||
|
updateTool,
|
||||||
|
hasUnsavedChanges,
|
||||||
|
canSaveAutomation,
|
||||||
|
getToolName,
|
||||||
|
getToolDefaultParameters,
|
||||||
|
getAutomatableTools,
|
||||||
|
isToolAutomatable
|
||||||
|
};
|
||||||
|
}
|
@ -1,11 +1,65 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { ToolRegistry } from '../data/toolsTaxonomy';
|
import { ToolRegistry } from '../data/toolsTaxonomy';
|
||||||
import { AutomationConfig, AutomationExecutionCallbacks } from '../types/automation';
|
|
||||||
import { AUTOMATION_CONSTANTS } from '../constants/automation';
|
import { AUTOMATION_CONSTANTS } from '../constants/automation';
|
||||||
import { AutomationFileProcessor } from './automationFileProcessor';
|
import { AutomationFileProcessor } from './automationFileProcessor';
|
||||||
import { ResourceManager } from './resourceManager';
|
import { ResourceManager } from './resourceManager';
|
||||||
import { ToolType } from '../hooks/tools/shared/useToolOperation';
|
import { ToolType } from '../hooks/tools/shared/useToolOperation';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute operation using a static config (for definition-based tools)
|
||||||
|
*/
|
||||||
|
const executeWithStaticConfig = async (
|
||||||
|
config: any,
|
||||||
|
parameters: any,
|
||||||
|
files: File[]
|
||||||
|
): Promise<File[]> => {
|
||||||
|
if (config.toolType === ToolType.singleFile) {
|
||||||
|
// Single file processing - process each file individually
|
||||||
|
const processedFiles: File[] = [];
|
||||||
|
for (const file of files) {
|
||||||
|
const endpoint = typeof config.endpoint === 'function'
|
||||||
|
? config.endpoint(parameters)
|
||||||
|
: config.endpoint;
|
||||||
|
|
||||||
|
const formData = config.buildFormData(parameters, file);
|
||||||
|
|
||||||
|
const response = await axios.post(endpoint, formData, {
|
||||||
|
responseType: 'blob',
|
||||||
|
timeout: AUTOMATION_CONSTANTS.OPERATION_TIMEOUT
|
||||||
|
});
|
||||||
|
|
||||||
|
const prefix = config.filePrefix || '';
|
||||||
|
const fileName = file.name.replace(/\.[^/.]+$/, '') + '.pdf';
|
||||||
|
const processedFile = new File([response.data], `${prefix}${fileName}`, { type: 'application/pdf' });
|
||||||
|
processedFiles.push(processedFile);
|
||||||
|
}
|
||||||
|
return processedFiles;
|
||||||
|
} else if (config.toolType === ToolType.multiFile) {
|
||||||
|
// Multi-file processing - single API call with all files
|
||||||
|
const endpoint = typeof config.endpoint === 'function'
|
||||||
|
? config.endpoint(parameters)
|
||||||
|
: config.endpoint;
|
||||||
|
|
||||||
|
const formData = config.buildFormData(parameters, files);
|
||||||
|
|
||||||
|
const response = await axios.post(endpoint, formData, {
|
||||||
|
responseType: 'blob',
|
||||||
|
timeout: AUTOMATION_CONSTANTS.OPERATION_TIMEOUT
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle multi-file response (usually ZIP)
|
||||||
|
if (response.data.type === 'application/pdf') {
|
||||||
|
const fileName = files[0]?.name || 'document.pdf';
|
||||||
|
return [new File([response.data], fileName, { type: 'application/pdf' })];
|
||||||
|
} else {
|
||||||
|
// Extract ZIP files
|
||||||
|
const { extractZipFiles } = await import('./automationFileProcessor');
|
||||||
|
return await extractZipFiles(response.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Unsupported tool type: ${config.toolType}`);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute a tool operation directly without using React hooks
|
* Execute a tool operation directly without using React hooks
|
||||||
@ -31,17 +85,44 @@ export const executeToolOperationWithPrefix = async (
|
|||||||
): Promise<File[]> => {
|
): Promise<File[]> => {
|
||||||
console.log(`🔧 Executing tool: ${operationName}`, { parameters, fileCount: files.length });
|
console.log(`🔧 Executing tool: ${operationName}`, { parameters, fileCount: files.length });
|
||||||
|
|
||||||
const config = toolRegistry[operationName as keyof ToolRegistry]?.operationConfig;
|
const toolInfo = toolRegistry[operationName as keyof ToolRegistry];
|
||||||
if (!config) {
|
const config = toolInfo?.operationConfig;
|
||||||
console.error(`❌ Tool operation not supported: ${operationName}`);
|
const definition = toolInfo?.definition;
|
||||||
|
|
||||||
|
if (!config && !definition) {
|
||||||
|
console.error(`❌ Tool operation not supported: ${operationName}. Missing both operationConfig and definition.`);
|
||||||
throw new Error(`Tool operation not supported: ${operationName}`);
|
throw new Error(`Tool operation not supported: ${operationName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`📋 Using config:`, config);
|
console.log(`📋 Using config:`, config, `definition:`, definition);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Handle definition-based tools by extracting their static execution logic
|
||||||
|
if (definition && !config) {
|
||||||
|
console.log(`🎯 Using definition-based tool: ${definition.id}`);
|
||||||
|
|
||||||
|
// Get the static operation config from the tool's operation hook
|
||||||
|
// This is exported as a static config object from each tool
|
||||||
|
const operationModule = await import(`../hooks/tools/${definition.id}/use${definition.id.charAt(0).toUpperCase() + definition.id.slice(1)}Operation`);
|
||||||
|
const staticConfig = operationModule[`${definition.id}OperationConfig`];
|
||||||
|
|
||||||
|
if (staticConfig) {
|
||||||
|
console.log(`📋 Using static config for ${definition.id}:`, staticConfig);
|
||||||
|
// Use the static config to execute the operation
|
||||||
|
if (staticConfig.customProcessor) {
|
||||||
|
const resultFiles = await staticConfig.customProcessor(parameters, files);
|
||||||
|
return resultFiles;
|
||||||
|
} else {
|
||||||
|
// Handle single/multi-file operations using the static config
|
||||||
|
return await executeWithStaticConfig(staticConfig, parameters, files);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(`Definition-based tool ${definition.id} does not have a static config`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check if tool uses custom processor (like Convert tool)
|
// Check if tool uses custom processor (like Convert tool)
|
||||||
if (config.customProcessor) {
|
if (config && config.customProcessor) {
|
||||||
console.log(`🎯 Using custom processor for ${config.operationType}`);
|
console.log(`🎯 Using custom processor for ${config.operationType}`);
|
||||||
const resultFiles = await config.customProcessor(parameters, files);
|
const resultFiles = await config.customProcessor(parameters, files);
|
||||||
console.log(`✅ Custom processor returned ${resultFiles.length} files`);
|
console.log(`✅ Custom processor returned ${resultFiles.length} files`);
|
||||||
|
151
frontend/src/utils/automationExecutorEnhanced.ts
Normal file
151
frontend/src/utils/automationExecutorEnhanced.ts
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
import { ToolRegistry } from '../data/toolsTaxonomy';
|
||||||
|
import { ToolDefinition } from '../components/tools/shared/toolDefinition';
|
||||||
|
import { AutomationTool } from '../types/automation';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enhanced automation executor that works with both definition-based and legacy tools
|
||||||
|
*/
|
||||||
|
export class EnhancedAutomationExecutor {
|
||||||
|
constructor(private toolRegistry: ToolRegistry) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a single automation step
|
||||||
|
*/
|
||||||
|
async executeStep(tool: AutomationTool, files: File[]): Promise<File[]> {
|
||||||
|
const toolEntry = this.toolRegistry[tool.operation as keyof ToolRegistry];
|
||||||
|
if (!toolEntry) {
|
||||||
|
throw new Error(`Tool ${tool.operation} not found in registry`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's a definition-based tool
|
||||||
|
if (toolEntry.definition) {
|
||||||
|
return this.executeDefinitionBasedStep(toolEntry.definition as ToolDefinition<unknown>, tool, files);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it has legacy operationConfig
|
||||||
|
if (toolEntry.operationConfig) {
|
||||||
|
return this.executeLegacyStep(toolEntry.operationConfig, tool, files);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Tool ${tool.operation} has no execution method available`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a step using a tool definition
|
||||||
|
*/
|
||||||
|
private async executeDefinitionBasedStep(
|
||||||
|
definition: ToolDefinition<unknown>,
|
||||||
|
tool: AutomationTool,
|
||||||
|
files: File[]
|
||||||
|
): Promise<File[]> {
|
||||||
|
// Create the operation hook instance
|
||||||
|
const operationHook = definition.useOperation();
|
||||||
|
|
||||||
|
// Execute the operation with the tool's parameters and files
|
||||||
|
await operationHook.executeOperation(tool.parameters, files);
|
||||||
|
|
||||||
|
// Return the resulting files
|
||||||
|
return operationHook.files;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a step using legacy operation config
|
||||||
|
*/
|
||||||
|
private async executeLegacyStep(
|
||||||
|
operationConfig: any,
|
||||||
|
tool: AutomationTool,
|
||||||
|
files: File[]
|
||||||
|
): Promise<File[]> {
|
||||||
|
// This would use the existing legacy execution logic
|
||||||
|
// Implementation depends on the current operationConfig structure
|
||||||
|
console.log('Executing legacy step:', tool.operation, tool.parameters);
|
||||||
|
|
||||||
|
// Placeholder - return files unchanged for now
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a complete automation workflow
|
||||||
|
*/
|
||||||
|
async executeWorkflow(tools: AutomationTool[], initialFiles: File[]): Promise<File[]> {
|
||||||
|
let currentFiles = initialFiles;
|
||||||
|
|
||||||
|
for (const tool of tools) {
|
||||||
|
try {
|
||||||
|
currentFiles = await this.executeStep(tool, currentFiles);
|
||||||
|
console.log(`Step ${tool.operation} completed, ${currentFiles.length} files`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Step ${tool.operation} failed:`, error);
|
||||||
|
throw new Error(`Automation failed at step: ${tool.operation}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get tool information for automation UI
|
||||||
|
*/
|
||||||
|
getToolInfo(operation: string) {
|
||||||
|
const toolEntry = this.toolRegistry[operation as keyof ToolRegistry];
|
||||||
|
if (!toolEntry) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: toolEntry.name,
|
||||||
|
hasDefinition: !!toolEntry.definition,
|
||||||
|
hasLegacyConfig: !!toolEntry.operationConfig,
|
||||||
|
isAutomatable: !!(toolEntry.definition || toolEntry.operationConfig)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get default parameters for a tool
|
||||||
|
*/
|
||||||
|
getDefaultParameters(operation: string): Record<string, unknown> {
|
||||||
|
const toolEntry = this.toolRegistry[operation as keyof ToolRegistry];
|
||||||
|
if (!toolEntry) return {};
|
||||||
|
|
||||||
|
if (toolEntry.definition) {
|
||||||
|
// For definition-based tools, we'd need to instantiate the parameters hook
|
||||||
|
// This is complex in a static context, so for now return empty object
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toolEntry.operationConfig?.defaultParameters) {
|
||||||
|
return { ...toolEntry.operationConfig.defaultParameters };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that an automation workflow is executable
|
||||||
|
*/
|
||||||
|
validateWorkflow(tools: AutomationTool[]): { isValid: boolean; errors: string[] } {
|
||||||
|
const errors: string[] = [];
|
||||||
|
|
||||||
|
for (const tool of tools) {
|
||||||
|
const toolInfo = this.getToolInfo(tool.operation);
|
||||||
|
|
||||||
|
if (!toolInfo) {
|
||||||
|
errors.push(`Tool '${tool.operation}' not found`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!toolInfo.isAutomatable) {
|
||||||
|
errors.push(`Tool '${tool.operation}' is not automatable`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tool.configured) {
|
||||||
|
errors.push(`Tool '${tool.operation}' is not configured`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isValid: errors.length === 0,
|
||||||
|
errors
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user