automation

This commit is contained in:
Anthony Stirling 2025-08-26 11:00:17 +01:00
parent c8b911366c
commit 1c043b60fb
5 changed files with 140 additions and 36 deletions

View File

@ -2110,13 +2110,24 @@
"automation": { "automation": {
"suggested": { "suggested": {
"securePdfIngestion": "Secure PDF Ingestion", "securePdfIngestion": "Secure PDF Ingestion",
"securePdfIngestionDesc": "Sanitise → OCR/Cleanup → PDF/A → Compress", "securePdfIngestionDesc": "Comprehensive PDF processing workflow that sanitizes documents, applies OCR with cleanup, converts to PDF/A format for long-term archival, and optimizes file size.",
"emailPreparation": "Email Preparation", "emailPreparation": "Email Preparation",
"emailPreparationDesc": "Compress → Split by Size 20MB → Sanitize metadata", "emailPreparationDesc": "Optimizes PDFs for email distribution by compressing files, splitting large documents into 20MB chunks for email compatibility, and removing metadata for privacy.",
"secureWorkflow": "Security Workflow", "secureWorkflow": "Security Workflow",
"secureWorkflowDesc": "Sanitize PDFs and add password protection", "secureWorkflowDesc": "Secures PDF documents by removing potentially malicious content like JavaScript and embedded files, then adds password protection to prevent unauthorized access.",
"optimizationWorkflow": "Optimization Workflow", "processImages": "Process Images",
"optimizationWorkflowDesc": "Repair and compress PDFs for better performance" "processImagesDesc": "Converts multiple image files into a single PDF document, then applies OCR technology to extract searchable text from the images."
},
"operation": {
"sanitize": "Sanitize",
"ocrCleanup": "OCR & Cleanup",
"pdfaConversion": "PDF/A Conversion",
"compress": "Compress",
"splitBySize": "Split by Size (20MB)",
"sanitizeMetadata": "Remove Metadata",
"addPassword": "Add Password Protection",
"imageToPdf": "Image to PDF",
"ocr": "OCR Text Extraction"
} }
}, },
"automate": { "automate": {

View File

@ -5,14 +5,17 @@ import MoreVertIcon from '@mui/icons-material/MoreVert';
import EditIcon from '@mui/icons-material/Edit'; import EditIcon from '@mui/icons-material/Edit';
import DeleteIcon from '@mui/icons-material/Delete'; import DeleteIcon from '@mui/icons-material/Delete';
import ContentCopyIcon from '@mui/icons-material/ContentCopy'; import ContentCopyIcon from '@mui/icons-material/ContentCopy';
import { Tooltip } from '../../shared/Tooltip';
interface AutomationEntryProps { interface AutomationEntryProps {
/** Optional title for the automation (usually for custom ones) */ /** Optional title for the automation (usually for custom ones) */
title?: string; title?: string;
/** Optional description for tooltip */
description?: string;
/** MUI Icon component for the badge */ /** MUI Icon component for the badge */
badgeIcon?: React.ComponentType<any>; badgeIcon?: React.ComponentType<any>;
/** Array of tool operation names in the workflow */ /** Array of tool operation names in the workflow OR full operation objects with display names */
operations: string[]; operations: string[] | Array<{operation: string; displayName?: string}>;
/** Click handler */ /** Click handler */
onClick: () => void; onClick: () => void;
/** Whether to keep the icon at normal color (for special cases like "Add New") */ /** Whether to keep the icon at normal color (for special cases like "Add New") */
@ -29,6 +32,7 @@ interface AutomationEntryProps {
export default function AutomationEntry({ export default function AutomationEntry({
title, title,
description,
badgeIcon: BadgeIcon, badgeIcon: BadgeIcon,
operations, operations,
onClick, onClick,
@ -45,6 +49,53 @@ export default function AutomationEntry({
// Keep item in hovered state if menu is open // Keep item in hovered state if menu is open
const shouldShowHovered = isHovered || isMenuOpen; const shouldShowHovered = isHovered || isMenuOpen;
// Create tooltip content with description and tool chain
const createTooltipContent = () => {
if (!description) return null;
const toolChain = operations.map((op, index) => {
// Handle both string[] and operation object arrays
const operationName = typeof op === 'string' ? op : op.operation;
const displayName = typeof op === 'object' && op.displayName ? op.displayName : t(`${operationName}.title`, operationName);
return (
<React.Fragment key={`${operationName}-${index}`}>
<Text
component="span"
size="sm"
fw={600}
style={{
color: 'var(--mantine-primary-color-filled)',
background: 'var(--mantine-primary-color-light)',
padding: '2px 6px',
borderRadius: '4px',
fontSize: '0.75rem',
whiteSpace: 'nowrap'
}}
>
{displayName}
</Text>
{index < operations.length - 1 && (
<Text component="span" size="sm" mx={4}>
</Text>
)}
</React.Fragment>
);
});
return (
<div style={{ minWidth: '400px', width: 'auto' }}>
<Text size="sm" mb={8} style={{ whiteSpace: 'normal', wordWrap: 'break-word' }}>
{description}
</Text>
<div style={{ display: 'flex', alignItems: 'center', gap: '4px', whiteSpace: 'nowrap' }}>
{toolChain}
</div>
</div>
);
};
const renderContent = () => { const renderContent = () => {
if (title) { if (title) {
// Custom automation with title // Custom automation with title
@ -74,26 +125,32 @@ export default function AutomationEntry({
/> />
)} )}
<Group gap="xs" justify="flex-start" style={{ flex: 1 }}> <Group gap="xs" justify="flex-start" style={{ flex: 1 }}>
{operations.map((op, index) => ( {operations.map((op, index) => {
<React.Fragment key={`${op}-${index}`}> // Handle both string[] and operation object arrays
<Text size="xs" style={{ color: 'var(--mantine-color-text)' }}> const operationName = typeof op === 'string' ? op : op.operation;
{t(`${op}.title`, op)} const displayName = typeof op === 'object' && op.displayName ? op.displayName : t(`${operationName}.title`, operationName);
</Text>
return (
{index < operations.length - 1 && ( <React.Fragment key={`${operationName}-${index}`}>
<Text size="xs" c="dimmed" style={{ color: 'var(--mantine-color-text)' }}> <Text size="xs" style={{ color: 'var(--mantine-color-text)' }}>
{displayName}
</Text> </Text>
)}
</React.Fragment> {index < operations.length - 1 && (
))} <Text size="xs" c="dimmed" style={{ color: 'var(--mantine-color-text)' }}>
</Text>
)}
</React.Fragment>
);
})}
</Group> </Group>
</Group> </Group>
); );
} }
}; };
return ( const boxContent = (
<Box <Box
style={{ style={{
backgroundColor: shouldShowHovered ? 'var(--mantine-color-gray-1)' : 'transparent', backgroundColor: shouldShowHovered ? 'var(--mantine-color-gray-1)' : 'transparent',
@ -175,4 +232,18 @@ export default function AutomationEntry({
</Group> </Group>
</Box> </Box>
); );
// Only show tooltip if description exists, otherwise return plain content
return description ? (
<Tooltip
content={createTooltipContent()}
position="right"
arrow={true}
delay={500}
>
{boxContent}
</Tooltip>
) : (
boxContent
);
} }

View File

@ -66,8 +66,9 @@ export default function AutomationSelection({
<AutomationEntry <AutomationEntry
key={automation.id} key={automation.id}
title={automation.name} title={automation.name}
description={automation.description}
badgeIcon={automation.icon} badgeIcon={automation.icon}
operations={automation.operations.map(op => op.operation)} operations={automation.operations}
onClick={() => onRun(automation)} onClick={() => onRun(automation)}
showMenu={true} showMenu={true}
onCopy={() => onCopyFromSuggested(automation)} onCopy={() => onCopyFromSuggested(automation)}

View File

@ -19,10 +19,11 @@ export function useSuggestedAutomations(): SuggestedAutomation[] {
{ {
id: "secure-pdf-ingestion", id: "secure-pdf-ingestion",
name: t("automation.suggested.securePdfIngestion", "Secure PDF Ingestion"), name: t("automation.suggested.securePdfIngestion", "Secure PDF Ingestion"),
description: t("automation.suggested.securePdfIngestionDesc", "Sanitise → OCR/Cleanup → PDF/A → Compress"), description: t("automation.suggested.securePdfIngestionDesc", "Comprehensive PDF processing workflow that sanitizes documents, applies OCR with cleanup, converts to PDF/A format for long-term archival, and optimizes file size."),
operations: [ operations: [
{ {
operation: "sanitize", operation: "sanitize",
displayName: t("automation.operation.sanitize", "Sanitize"),
parameters: { parameters: {
removeJavaScript: true, removeJavaScript: true,
removeEmbeddedFiles: true, removeEmbeddedFiles: true,
@ -34,6 +35,7 @@ export function useSuggestedAutomations(): SuggestedAutomation[] {
}, },
{ {
operation: "ocr", operation: "ocr",
displayName: t("automation.operation.ocrCleanup", "OCR & Cleanup"),
parameters: { parameters: {
languages: ['eng'], languages: ['eng'],
ocrType: 'skip-text', ocrType: 'skip-text',
@ -43,6 +45,7 @@ export function useSuggestedAutomations(): SuggestedAutomation[] {
}, },
{ {
operation: "convert", operation: "convert",
displayName: t("automation.operation.pdfaConversion", "PDF/A Conversion"),
parameters: { parameters: {
fromExtension: 'pdf', fromExtension: 'pdf',
toExtension: 'pdfa', toExtension: 'pdfa',
@ -53,6 +56,7 @@ export function useSuggestedAutomations(): SuggestedAutomation[] {
}, },
{ {
operation: "compress", operation: "compress",
displayName: t("automation.operation.compress", "Compress"),
parameters: { parameters: {
compressionLevel: 5, compressionLevel: 5,
grayscale: false, grayscale: false,
@ -70,10 +74,11 @@ export function useSuggestedAutomations(): SuggestedAutomation[] {
{ {
id: "email-preparation", id: "email-preparation",
name: t("automation.suggested.emailPreparation", "Email Preparation"), name: t("automation.suggested.emailPreparation", "Email Preparation"),
description: t("automation.suggested.emailPreparationDesc", "Compress → Split by Size 20MB → Sanitize metadata"), description: t("automation.suggested.emailPreparationDesc", "Optimizes PDFs for email distribution by compressing files, splitting large documents into 20MB chunks for email compatibility, and removing metadata for privacy."),
operations: [ operations: [
{ {
operation: "compress", operation: "compress",
displayName: t("automation.operation.compress", "Compress"),
parameters: { parameters: {
compressionLevel: 5, compressionLevel: 5,
grayscale: false, grayscale: false,
@ -85,6 +90,7 @@ export function useSuggestedAutomations(): SuggestedAutomation[] {
}, },
{ {
operation: "splitPdf", operation: "splitPdf",
displayName: t("automation.operation.splitBySize", "Split by Size (20MB)"),
parameters: { parameters: {
mode: 'bySizeOrCount', mode: 'bySizeOrCount',
pages: '', pages: '',
@ -100,6 +106,7 @@ export function useSuggestedAutomations(): SuggestedAutomation[] {
}, },
{ {
operation: "sanitize", operation: "sanitize",
displayName: t("automation.operation.sanitizeMetadata", "Remove Metadata"),
parameters: { parameters: {
removeJavaScript: false, removeJavaScript: false,
removeEmbeddedFiles: false, removeEmbeddedFiles: false,
@ -117,10 +124,11 @@ export function useSuggestedAutomations(): SuggestedAutomation[] {
{ {
id: "secure-workflow", id: "secure-workflow",
name: t("automation.suggested.secureWorkflow", "Security Workflow"), name: t("automation.suggested.secureWorkflow", "Security Workflow"),
description: t("automation.suggested.secureWorkflowDesc", "Sanitize PDFs and add password protection"), description: t("automation.suggested.secureWorkflowDesc", "Secures PDF documents by removing potentially malicious content like JavaScript and embedded files, then adds password protection to prevent unauthorized access."),
operations: [ operations: [
{ {
operation: "sanitize", operation: "sanitize",
displayName: t("automation.operation.sanitize", "Sanitize"),
parameters: { parameters: {
removeJavaScript: true, removeJavaScript: true,
removeEmbeddedFiles: true, removeEmbeddedFiles: true,
@ -132,6 +140,7 @@ export function useSuggestedAutomations(): SuggestedAutomation[] {
}, },
{ {
operation: "addPassword", operation: "addPassword",
displayName: t("automation.operation.addPassword", "Add Password Protection"),
parameters: { parameters: {
password: 'password', password: 'password',
ownerPassword: '', ownerPassword: '',
@ -154,23 +163,34 @@ export function useSuggestedAutomations(): SuggestedAutomation[] {
icon: SecurityIcon, icon: SecurityIcon,
}, },
{ {
id: "optimization-workflow", id: "process-images",
name: t("automation.suggested.optimizationWorkflow", "Optimization Workflow"), name: t("automation.suggested.processImages", "Process Images"),
description: t("automation.suggested.optimizationWorkflowDesc", "Repair and compress PDFs for better performance"), description: t("automation.suggested.processImagesDesc", "Converts multiple image files into a single PDF document, then applies OCR technology to extract searchable text from the images."),
operations: [ operations: [
{ {
operation: "repair", operation: "convert",
parameters: {} displayName: t("automation.operation.imageToPdf", "Image to PDF"),
parameters: {
fromExtension: 'image',
toExtension: 'pdf',
imageOptions: {
colorType: 'color',
dpi: 300,
singleOrMultiple: 'multiple',
fitOption: 'maintainAspectRatio',
autoRotate: true,
combineImages: true,
}
}
}, },
{ {
operation: "compress", operation: "ocr",
displayName: t("automation.operation.ocr", "OCR Text Extraction"),
parameters: { parameters: {
compressionLevel: 7, languages: ['eng'],
grayscale: false, ocrType: 'skip-text',
expectedSize: '', ocrRenderType: 'hocr',
compressionMethod: 'quality', additionalOptions: [],
fileSizeValue: '',
fileSizeUnit: 'MB',
} }
} }
], ],

View File

@ -5,6 +5,7 @@
export interface AutomationOperation { export interface AutomationOperation {
operation: string; operation: string;
parameters: Record<string, any>; parameters: Record<string, any>;
displayName?: string; // Custom display name for tooltip
} }
export interface AutomationConfig { export interface AutomationConfig {