This commit is contained in:
Anthony Stirling 2025-08-26 10:17:42 +01:00
parent fe9d2367d5
commit c8b911366c
6 changed files with 140 additions and 32 deletions

View File

@ -2106,5 +2106,20 @@
"results": {
"title": "Decrypted PDFs"
}
},
"automation": {
"suggested": {
"securePdfIngestion": "Secure PDF Ingestion",
"securePdfIngestionDesc": "Sanitise → OCR/Cleanup → PDF/A → Compress",
"emailPreparation": "Email Preparation",
"emailPreparationDesc": "Compress → Split by Size 20MB → Sanitize metadata",
"secureWorkflow": "Security Workflow",
"secureWorkflowDesc": "Sanitize PDFs and add password protection",
"optimizationWorkflow": "Optimization Workflow",
"optimizationWorkflowDesc": "Repair and compress PDFs for better performance"
}
},
"automate": {
"copyToSaved": "Copy to Saved"
}
}

View File

@ -4,6 +4,7 @@ import { Button, Group, Text, ActionIcon, Menu, Box } from '@mantine/core';
import MoreVertIcon from '@mui/icons-material/MoreVert';
import EditIcon from '@mui/icons-material/Edit';
import DeleteIcon from '@mui/icons-material/Delete';
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
interface AutomationEntryProps {
/** Optional title for the automation (usually for custom ones) */
@ -22,6 +23,8 @@ interface AutomationEntryProps {
onEdit?: () => void;
/** Delete handler */
onDelete?: () => void;
/** Copy handler (for suggested automations) */
onCopy?: () => void;
}
export default function AutomationEntry({
@ -32,7 +35,8 @@ export default function AutomationEntry({
keepIconColor = false,
showMenu = false,
onEdit,
onDelete
onDelete,
onCopy
}: AutomationEntryProps) {
const { t } = useTranslation();
const [isHovered, setIsHovered] = useState(false);
@ -132,6 +136,17 @@ export default function AutomationEntry({
</Menu.Target>
<Menu.Dropdown>
{onCopy && (
<Menu.Item
leftSection={<ContentCopyIcon style={{ fontSize: 16 }} />}
onClick={(e) => {
e.stopPropagation();
onCopy();
}}
>
{t('automate.copyToSaved', 'Copy to Saved')}
</Menu.Item>
)}
{onEdit && (
<Menu.Item
leftSection={<EditIcon style={{ fontSize: 16 }} />}

View File

@ -5,7 +5,7 @@ import AddCircleOutline from "@mui/icons-material/AddCircleOutline";
import SettingsIcon from "@mui/icons-material/Settings";
import AutomationEntry from "./AutomationEntry";
import { useSuggestedAutomations } from "../../../hooks/tools/automate/useSuggestedAutomations";
import { AutomationConfig } from "../../../types/automation";
import { AutomationConfig, SuggestedAutomation } from "../../../types/automation";
interface AutomationSelectionProps {
savedAutomations: AutomationConfig[];
@ -13,6 +13,7 @@ interface AutomationSelectionProps {
onRun: (automation: AutomationConfig) => void;
onEdit: (automation: AutomationConfig) => void;
onDelete: (automation: AutomationConfig) => void;
onCopyFromSuggested: (automation: SuggestedAutomation) => void;
}
export default function AutomationSelection({
@ -20,7 +21,8 @@ export default function AutomationSelection({
onCreateNew,
onRun,
onEdit,
onDelete
onDelete,
onCopyFromSuggested
}: AutomationSelectionProps) {
const { t } = useTranslation();
const suggestedAutomations = useSuggestedAutomations();
@ -63,9 +65,12 @@ export default function AutomationSelection({
{suggestedAutomations.map((automation) => (
<AutomationEntry
key={automation.id}
title={automation.name}
badgeIcon={automation.icon}
operations={automation.operations.map(op => op.operation)}
onClick={() => onRun(automation)}
showMenu={true}
onCopy={() => onCopyFromSuggested(automation)}
/>
))}
</Stack>

View File

@ -1,5 +1,6 @@
import { useState, useEffect, useCallback } from 'react';
import { AutomationConfig } from '../../../services/automationStorage';
import { SuggestedAutomation } from '../../../types/automation';
export interface SavedAutomation extends AutomationConfig {}
@ -40,6 +41,26 @@ export function useSavedAutomations() {
}
}, [refreshAutomations]);
const copyFromSuggested = useCallback(async (suggestedAutomation: SuggestedAutomation) => {
try {
const { automationStorage } = await import('../../../services/automationStorage');
// Convert suggested automation to saved automation format
const savedAutomation = {
name: suggestedAutomation.name,
description: suggestedAutomation.description,
operations: suggestedAutomation.operations
};
await automationStorage.saveAutomation(savedAutomation);
// Refresh the list after saving
refreshAutomations();
} catch (err) {
console.error('Error copying suggested automation:', err);
throw err;
}
}, [refreshAutomations]);
// Load automations on mount
useEffect(() => {
loadSavedAutomations();
@ -50,6 +71,7 @@ export function useSavedAutomations() {
loading,
error,
refreshAutomations,
deleteAutomation
deleteAutomation,
copyFromSuggested
};
}

View File

@ -17,9 +17,60 @@ export function useSuggestedAutomations(): SuggestedAutomation[] {
const now = new Date().toISOString();
return [
{
id: "compress-and-split",
name: t("automation.suggested.compressAndSplit", "Compress & Split"),
description: t("automation.suggested.compressAndSplitDesc", "Compress PDFs and split them by pages"),
id: "secure-pdf-ingestion",
name: t("automation.suggested.securePdfIngestion", "Secure PDF Ingestion"),
description: t("automation.suggested.securePdfIngestionDesc", "Sanitise → OCR/Cleanup → PDF/A → Compress"),
operations: [
{
operation: "sanitize",
parameters: {
removeJavaScript: true,
removeEmbeddedFiles: true,
removeXMPMetadata: true,
removeMetadata: true,
removeLinks: false,
removeFonts: false,
}
},
{
operation: "ocr",
parameters: {
languages: ['eng'],
ocrType: 'skip-text',
ocrRenderType: 'hocr',
additionalOptions: ['clean', 'cleanFinal'],
}
},
{
operation: "convert",
parameters: {
fromExtension: 'pdf',
toExtension: 'pdfa',
pdfaOptions: {
outputFormat: 'pdfa-1',
}
}
},
{
operation: "compress",
parameters: {
compressionLevel: 5,
grayscale: false,
expectedSize: '',
compressionMethod: 'quality',
fileSizeValue: '',
fileSizeUnit: 'MB',
}
}
],
createdAt: now,
updatedAt: now,
icon: SecurityIcon,
},
{
id: "email-preparation",
name: t("automation.suggested.emailPreparation", "Email Preparation"),
description: t("automation.suggested.emailPreparationDesc", "Compress → Split by Size 20MB → Sanitize metadata"),
operations: [
{
operation: "compress",
@ -36,41 +87,33 @@ export function useSuggestedAutomations(): SuggestedAutomation[] {
operation: "splitPdf",
parameters: {
mode: 'bySizeOrCount',
pages: '1',
hDiv: '2',
vDiv: '2',
pages: '',
hDiv: '1',
vDiv: '1',
merge: false,
splitType: 'pages',
splitValue: '1',
splitType: 'size',
splitValue: '20MB',
bookmarkLevel: '1',
includeMetadata: false,
allowDuplicates: false,
}
},
{
operation: "sanitize",
parameters: {
removeJavaScript: false,
removeEmbeddedFiles: false,
removeXMPMetadata: true,
removeMetadata: true,
removeLinks: false,
removeFonts: false,
}
}
],
createdAt: now,
updatedAt: now,
icon: CompressIcon,
},
{
id: "ocr-workflow",
name: t("automation.suggested.ocrWorkflow", "OCR Processing"),
description: t("automation.suggested.ocrWorkflowDesc", "Extract text from PDFs using OCR technology"),
operations: [
{
operation: "ocr",
parameters: {
languages: ['eng'],
ocrType: 'skip-text',
ocrRenderType: 'hocr',
additionalOptions: [],
}
}
],
createdAt: now,
updatedAt: now,
icon: TextFieldsIcon,
},
{
id: "secure-workflow",
name: t("automation.suggested.secureWorkflow", "Security Workflow"),

View File

@ -28,7 +28,7 @@ const Automate = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const automateOperation = useAutomateOperation();
const toolRegistry = useFlatToolRegistry();
const hasResults = automateOperation.files.length > 0 || automateOperation.downloadUrl !== null;
const { savedAutomations, deleteAutomation, refreshAutomations } = useSavedAutomations();
const { savedAutomations, deleteAutomation, refreshAutomations, copyFromSuggested } = useSavedAutomations();
const handleStepChange = (data: AutomationStepData) => {
// If navigating away from run step, reset automation results
@ -79,6 +79,14 @@ const Automate = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
onError?.(`Failed to delete automation: ${automation.name}`);
}
}}
onCopyFromSuggested={async (suggestedAutomation) => {
try {
await copyFromSuggested(suggestedAutomation);
} catch (error) {
console.error('Failed to copy suggested automation:', error);
onError?.(`Failed to copy automation: ${suggestedAutomation.name}`);
}
}}
/>
);