Demo working for few tools

This commit is contained in:
Connor Yoh 2025-08-21 12:53:28 +01:00
parent 555d937921
commit 4956d6b4da
4 changed files with 103 additions and 123 deletions

View File

@ -1,27 +1,15 @@
import React, { useState } from 'react'; import React, { useState } from "react";
import { useTranslation } from 'react-i18next'; import { useTranslation } from "react-i18next";
import { import { Button, Text, Stack, Group, Progress, Card } from "@mantine/core";
Button, import PlayArrowIcon from "@mui/icons-material/PlayArrow";
Text, import CheckIcon from "@mui/icons-material/Check";
Title, import ErrorIcon from "@mui/icons-material/Error";
Stack, import { useFileSelection } from "../../../contexts/FileSelectionContext";
Group, import { useFlatToolRegistry } from "../../../data/useTranslatedToolRegistry";
ActionIcon, import { executeAutomationSequence } from "../../../utils/automationExecutor";
Progress,
Card,
Alert
} from '@mantine/core';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
import CheckIcon from '@mui/icons-material/Check';
import ErrorIcon from '@mui/icons-material/Error';
import { useFileContext } from '../../../contexts/FileContext';
import { useFlatToolRegistry } from '../../../data/useTranslatedToolRegistry';
import { executeAutomationSequence } from '../../../utils/automationExecutor';
interface AutomationRunProps { interface AutomationRunProps {
automation: any; automation: any;
onBack: () => void;
onComplete: () => void; onComplete: () => void;
automateOperation?: any; // Add the operation hook to store results automateOperation?: any; // Add the operation hook to store results
} }
@ -30,18 +18,17 @@ interface ExecutionStep {
id: string; id: string;
operation: string; operation: string;
name: string; name: string;
status: 'pending' | 'running' | 'completed' | 'error'; status: "pending" | "running" | "completed" | "error";
error?: string; error?: string;
} }
export default function AutomationRun({ automation, onBack, onComplete, automateOperation }: AutomationRunProps) { export default function AutomationRun({ automation, onComplete, automateOperation }: AutomationRunProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const { activeFiles, consumeFiles } = useFileContext(); const { selectedFiles } = useFileSelection();
const toolRegistry = useFlatToolRegistry(); const toolRegistry = useFlatToolRegistry();
const [isExecuting, setIsExecuting] = useState(false); const [isExecuting, setIsExecuting] = useState(false);
const [executionSteps, setExecutionSteps] = useState<ExecutionStep[]>([]); const [executionSteps, setExecutionSteps] = useState<ExecutionStep[]>([]);
const [currentStepIndex, setCurrentStepIndex] = useState(-1); const [currentStepIndex, setCurrentStepIndex] = useState(-1);
const [currentFiles, setCurrentFiles] = useState<File[]>([]);
// Initialize execution steps from automation // Initialize execution steps from automation
React.useEffect(() => { React.useEffect(() => {
@ -52,74 +39,41 @@ export default function AutomationRun({ automation, onBack, onComplete, automate
id: `${op.operation}-${index}`, id: `${op.operation}-${index}`,
operation: op.operation, operation: op.operation,
name: tool?.name || op.operation, name: tool?.name || op.operation,
status: 'pending' as const status: "pending" as const,
}; };
}); });
setExecutionSteps(steps); setExecutionSteps(steps);
} }
}, [automation]); // Remove toolRegistry from dependencies to prevent infinite loops }, [automation]); // Remove toolRegistry from dependencies to prevent infinite loops
// Initialize current files with active files (separate effect)
React.useEffect(() => {
if (activeFiles && activeFiles.length > 0) {
setCurrentFiles([...activeFiles]);
}
}, [activeFiles?.length]); // Only depend on length to avoid infinite loops
const executeAutomation = async () => { const executeAutomation = async () => {
if (!activeFiles || activeFiles.length === 0) { if (!selectedFiles || selectedFiles.length === 0) {
// Show error - need files to execute automation // Show error - need files to execute automation
return; return;
} }
if (!automateOperation) {
console.error('No automateOperation provided');
return;
}
setIsExecuting(true); setIsExecuting(true);
setCurrentStepIndex(0); setCurrentStepIndex(0);
try { try {
// Execute the automation sequence using the new executor // Use the automateOperation.executeOperation to handle file consumption properly
const finalResults = await executeAutomationSequence( await automateOperation.executeOperation(
automation, { automationConfig: automation },
activeFiles, selectedFiles
(stepIndex: number, operationName: string) => {
// Step started
setCurrentStepIndex(stepIndex);
setExecutionSteps(prev => prev.map((step, idx) =>
idx === stepIndex ? { ...step, status: 'running' } : step
));
},
(stepIndex: number, resultFiles: File[]) => {
// Step completed
setExecutionSteps(prev => prev.map((step, idx) =>
idx === stepIndex ? { ...step, status: 'completed' } : step
));
setCurrentFiles(resultFiles);
},
(stepIndex: number, error: string) => {
// Step failed
setExecutionSteps(prev => prev.map((step, idx) =>
idx === stepIndex ? { ...step, status: 'error', error } : step
));
}
); );
// All steps completed successfully // All steps completed successfully
setCurrentStepIndex(-1); setCurrentStepIndex(-1);
setIsExecuting(false); setIsExecuting(false);
setCurrentFiles(finalResults);
// Properly integrate results with FileContext
if (finalResults.length > 0) {
console.log(`🎨 Integrating ${finalResults.length} result files with FileContext`);
// Use FileContext's consumeFiles to properly add results
// This replaces input files with output files (like other tools do)
await consumeFiles(activeFiles, finalResults);
console.log(`✅ Successfully integrated automation results with FileContext`);
}
console.log(`✅ Automation completed successfully`);
} catch (error: any) { } catch (error: any) {
console.error('Automation execution failed:', error); console.error("Automation execution failed:", error);
setIsExecuting(false); setIsExecuting(false);
setCurrentStepIndex(-1); setCurrentStepIndex(-1);
} }
@ -127,24 +81,35 @@ export default function AutomationRun({ automation, onBack, onComplete, automate
const getStepIcon = (step: ExecutionStep) => { const getStepIcon = (step: ExecutionStep) => {
switch (step.status) { switch (step.status) {
case 'completed': case "completed":
return <CheckIcon style={{ fontSize: 16, color: 'green' }} />; return <CheckIcon style={{ fontSize: 16, color: "green" }} />;
case 'error': case "error":
return <ErrorIcon style={{ fontSize: 16, color: 'red' }} />; return <ErrorIcon style={{ fontSize: 16, color: "red" }} />;
case 'running': case "running":
return <div style={{ width: 16, height: 16, border: '2px solid #ccc', borderTop: '2px solid #007bff', borderRadius: '50%', animation: 'spin 1s linear infinite' }} />; return (
<div
style={{
width: 16,
height: 16,
border: "2px solid #ccc",
borderTop: "2px solid #007bff",
borderRadius: "50%",
animation: "spin 1s linear infinite",
}}
/>
);
default: default:
return <div style={{ width: 16, height: 16, border: '2px solid #ccc', borderRadius: '50%' }} />; return <div style={{ width: 16, height: 16, border: "2px solid #ccc", borderRadius: "50%" }} />;
} }
}; };
const getProgress = () => { const getProgress = () => {
const completedSteps = executionSteps.filter(step => step.status === 'completed').length; const completedSteps = executionSteps.filter((step) => step.status === "completed").length;
return (completedSteps / executionSteps.length) * 100; return (completedSteps / executionSteps.length) * 100;
}; };
const allStepsCompleted = executionSteps.every(step => step.status === 'completed'); const allStepsCompleted = executionSteps.every((step) => step.status === "completed");
const hasErrors = executionSteps.some(step => step.status === 'error'); const hasErrors = executionSteps.some((step) => step.status === "error");
return ( return (
<div> <div>
@ -152,10 +117,10 @@ export default function AutomationRun({ automation, onBack, onComplete, automate
{/* Automation Info */} {/* Automation Info */}
<Card padding="md" withBorder> <Card padding="md" withBorder>
<Text size="sm" fw={500} mb="xs"> <Text size="sm" fw={500} mb="xs">
{automation?.name || t('automate.sequence.unnamed', 'Unnamed Automation')} {automation?.name || t("automate.sequence.unnamed", "Unnamed Automation")}
</Text> </Text>
<Text size="xs" c="dimmed"> <Text size="xs" c="dimmed">
{t('automate.sequence.steps', '{{count}} steps', { count: executionSteps.length })} {t("automate.sequence.steps", "{{count}} steps", { count: executionSteps.length })}
</Text> </Text>
</Card> </Card>
@ -163,9 +128,9 @@ export default function AutomationRun({ automation, onBack, onComplete, automate
{isExecuting && ( {isExecuting && (
<div> <div>
<Text size="sm" mb="xs"> <Text size="sm" mb="xs">
{t('automate.sequence.progress', 'Progress: {{current}}/{{total}}', { {t("automate.sequence.progress", "Progress: {{current}}/{{total}}", {
current: currentStepIndex + 1, current: currentStepIndex + 1,
total: executionSteps.length total: executionSteps.length,
})} })}
</Text> </Text>
<Progress value={getProgress()} size="lg" /> <Progress value={getProgress()} size="lg" />
@ -176,17 +141,20 @@ export default function AutomationRun({ automation, onBack, onComplete, automate
<Stack gap="xs"> <Stack gap="xs">
{executionSteps.map((step, index) => ( {executionSteps.map((step, index) => (
<Group key={step.id} gap="sm" align="center"> <Group key={step.id} gap="sm" align="center">
<Text size="xs" c="dimmed" style={{ minWidth: '1rem', textAlign: 'center' }}> <Text size="xs" c="dimmed" style={{ minWidth: "1rem", textAlign: "center" }}>
{index + 1} {index + 1}
</Text> </Text>
{getStepIcon(step)} {getStepIcon(step)}
<div style={{ flex: 1 }}> <div style={{ flex: 1 }}>
<Text size="sm" style={{ <Text
color: step.status === 'running' ? 'var(--mantine-color-blue-6)' : 'var(--mantine-color-text)', size="sm"
fontWeight: step.status === 'running' ? 500 : 400 style={{
}}> color: step.status === "running" ? "var(--mantine-color-blue-6)" : "var(--mantine-color-text)",
fontWeight: step.status === "running" ? 500 : 400,
}}
>
{step.name} {step.name}
</Text> </Text>
{step.error && ( {step.error && (
@ -204,23 +172,13 @@ export default function AutomationRun({ automation, onBack, onComplete, automate
<Button <Button
leftSection={<PlayArrowIcon />} leftSection={<PlayArrowIcon />}
onClick={executeAutomation} onClick={executeAutomation}
disabled={isExecuting || !activeFiles || activeFiles.length === 0} disabled={isExecuting || !selectedFiles || selectedFiles.length === 0}
loading={isExecuting} loading={isExecuting}
> >
{isExecuting {isExecuting
? t('automate.sequence.running', 'Running Automation...') ? t("automate.sequence.running", "Running Automation...")
: t('automate.sequence.run', 'Run Automation') : t("automate.sequence.run", "Run Automation")}
}
</Button> </Button>
{(allStepsCompleted || hasErrors) && (
<Button
variant="light"
onClick={onComplete}
>
{t('automate.sequence.finish', 'Finish')}
</Button>
)}
</Group> </Group>
</Stack> </Stack>

View File

@ -21,6 +21,7 @@ const showPlaceholderTools = false; // For development purposes. Allows seeing t
export function useFlatToolRegistry(): ToolRegistry { export function useFlatToolRegistry(): ToolRegistry {
const { t } = useTranslation(); const { t } = useTranslation();
return useMemo(() => {
const allTools: ToolRegistry = { const allTools: ToolRegistry = {
// Signing // Signing
@ -631,4 +632,5 @@ export function useFlatToolRegistry(): ToolRegistry {
}, {} as ToolRegistry); }, {} as ToolRegistry);
return filteredTools; return filteredTools;
} }
}, [t]); // Only re-compute when translations change
} }

View File

@ -1,5 +1,6 @@
import { useToolOperation } from '../shared/useToolOperation'; import { useToolOperation } from '../shared/useToolOperation';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { executeAutomationSequence } from '../../../utils/automationExecutor';
interface AutomateParameters { interface AutomateParameters {
automationConfig?: any; automationConfig?: any;
@ -7,16 +8,36 @@ interface AutomateParameters {
export function useAutomateOperation() { export function useAutomateOperation() {
const customProcessor = useCallback(async (params: AutomateParameters, files: File[]) => { const customProcessor = useCallback(async (params: AutomateParameters, files: File[]) => {
// For now, this is a placeholder - the automation execution will be implemented later console.log('🚀 Starting automation execution via customProcessor', { params, files });
// This function would send the automation config to the backend pipeline endpoint
console.log('Automation execution not yet implemented', { params, files }); if (!params.automationConfig) {
throw new Error('Automation execution not yet implemented'); throw new Error('No automation configuration provided');
}
// Execute the automation sequence and return the final results
const finalResults = await executeAutomationSequence(
params.automationConfig,
files,
(stepIndex: number, operationName: string) => {
console.log(`Step ${stepIndex + 1} started: ${operationName}`);
},
(stepIndex: number, resultFiles: File[]) => {
console.log(`Step ${stepIndex + 1} completed with ${resultFiles.length} files`);
},
(stepIndex: number, error: string) => {
console.error(`Step ${stepIndex + 1} failed:`, error);
throw new Error(`Automation step ${stepIndex + 1} failed: ${error}`);
}
);
console.log(`✅ Automation completed, returning ${finalResults.length} files`);
return finalResults;
}, []); }, []);
return useToolOperation<AutomateParameters>({ return useToolOperation<AutomateParameters>({
operationType: 'automate', operationType: 'automate',
endpoint: '/api/v1/pipeline/handleData', endpoint: '/api/v1/pipeline/handleData', // Not used with customProcessor
buildFormData: () => new FormData(), // Placeholder, not used with customProcessor buildFormData: () => new FormData(), // Not used with customProcessor
customProcessor, customProcessor,
filePrefix: 'automated_' filePrefix: 'automated_'
}); });

View File

@ -74,7 +74,6 @@ const Automate = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
return ( return (
<AutomationRun <AutomationRun
automation={stepData.automation} automation={stepData.automation}
onBack={() => handleStepChange({ step: 'selection'})}
onComplete={handleComplete} onComplete={handleComplete}
automateOperation={automateOperation} automateOperation={automateOperation}
/> />
@ -121,7 +120,7 @@ const Automate = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
// Run step // Run step
createStep(t('automate.run.title', 'Run Automation'), { createStep(t('automate.run.title', 'Run Automation'), {
isVisible: currentStep === 'run', isVisible: currentStep === 'run',
isCollapsed: false isCollapsed: hasResults,
}, currentStep === 'run' ? renderCurrentStep() : null) }, currentStep === 'run' ? renderCurrentStep() : null)
]; ];