From 9a2fd952b11885570e364f096ead15d005cb2588 Mon Sep 17 00:00:00 2001 From: Connor Yoh Date: Tue, 19 Aug 2025 12:46:09 +0100 Subject: [PATCH] first pass --- .../src/components/shared/QuickAccessBar.tsx | 17 +- .../tools/automate/AutomationCreation.tsx | 308 ++++++++++++++++++ .../tools/automate/AutomationSelection.tsx | 134 ++++++++ .../tools/automate/ToolConfigurationModal.tsx | 230 +++++++++++++ .../tools/automate/useAutomateOperation.ts | 23 ++ frontend/src/hooks/useToolManagement.tsx | 10 + frontend/src/services/automationStorage.ts | 183 +++++++++++ frontend/src/tools/Automate.tsx | 82 +++++ 8 files changed, 985 insertions(+), 2 deletions(-) create mode 100644 frontend/src/components/tools/automate/AutomationCreation.tsx create mode 100644 frontend/src/components/tools/automate/AutomationSelection.tsx create mode 100644 frontend/src/components/tools/automate/ToolConfigurationModal.tsx create mode 100644 frontend/src/hooks/tools/automate/useAutomateOperation.ts create mode 100644 frontend/src/services/automationStorage.ts create mode 100644 frontend/src/tools/Automate.tsx diff --git a/frontend/src/components/shared/QuickAccessBar.tsx b/frontend/src/components/shared/QuickAccessBar.tsx index 7aed3632b..4bfa717d1 100644 --- a/frontend/src/components/shared/QuickAccessBar.tsx +++ b/frontend/src/components/shared/QuickAccessBar.tsx @@ -86,12 +86,22 @@ const QuickAccessBar = forwardRef(({ }, ref) => { const { isRainbowMode } = useRainbowThemeContext(); const { openFilesModal, isFilesModalOpen } = useFilesModalContext(); - const { handleReaderToggle } = useToolWorkflow(); + const { handleReaderToggle, handleToolSelect, selectedToolKey } = useToolWorkflow(); const [configModalOpen, setConfigModalOpen] = useState(false); const [activeButton, setActiveButton] = useState('tools'); const scrollableRef = useRef(null); const isOverflow = useIsOverflowing(scrollableRef); + // Sync activeButton with selectedToolKey + React.useEffect(() => { + if (selectedToolKey === 'automate') { + setActiveButton('automate'); + } else if (selectedToolKey) { + // If any other tool is selected, default to 'tools' view + setActiveButton('tools'); + } + }, [selectedToolKey]); + const handleFilesButtonClick = () => { openFilesModal(); }; @@ -131,7 +141,10 @@ const QuickAccessBar = forwardRef(({ size: 'lg', isRound: false, type: 'navigation', - onClick: () => setActiveButton('automate') + onClick: () => { + setActiveButton('automate'); + handleToolSelect('automate'); + } }, { id: 'files', diff --git a/frontend/src/components/tools/automate/AutomationCreation.tsx b/frontend/src/components/tools/automate/AutomationCreation.tsx new file mode 100644 index 000000000..28c90bf0e --- /dev/null +++ b/frontend/src/components/tools/automate/AutomationCreation.tsx @@ -0,0 +1,308 @@ +import React, { useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + Button, + Card, + Text, + Title, + Stack, + Group, + Select, + TextInput, + Textarea, + Badge, + ActionIcon, + Modal, + Box +} from '@mantine/core'; +import AddIcon from '@mui/icons-material/Add'; +import DeleteIcon from '@mui/icons-material/Delete'; +import SettingsIcon from '@mui/icons-material/Settings'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import CheckIcon from '@mui/icons-material/Check'; +import CloseIcon from '@mui/icons-material/Close'; +import { useToolWorkflow } from '../../../contexts/ToolWorkflowContext'; +import ToolConfigurationModal from './ToolConfigurationModal'; + +interface AutomationCreationProps { + mode: 'custom' | 'suggested' | 'create'; + existingAutomation?: any; + onBack: () => void; + onComplete: () => void; +} + +interface AutomationTool { + id: string; + operation: string; + name: string; + configured: boolean; + parameters?: any; +} + +export default function AutomationCreation({ mode, existingAutomation, onBack, onComplete }: AutomationCreationProps) { + const { t } = useTranslation(); + const { toolRegistry } = useToolWorkflow(); + + const [automationName, setAutomationName] = useState(''); + const [automationDescription, setAutomationDescription] = useState(''); + const [selectedTools, setSelectedTools] = useState([]); + const [configModalOpen, setConfigModalOpen] = useState(false); + const [configuraingToolIndex, setConfiguringToolIndex] = useState(-1); + + // Initialize based on mode and existing automation + useEffect(() => { + if (mode === 'suggested' && existingAutomation) { + setAutomationName(existingAutomation.name); + setAutomationDescription(existingAutomation.description || ''); + + const tools = existingAutomation.operations.map((op: string) => ({ + id: `${op}-${Date.now()}`, + operation: op, + name: getToolName(op), + configured: false, + parameters: {} + })); + + setSelectedTools(tools); + } + }, [mode, existingAutomation]); + + const getToolName = (operation: string) => { + const tool = toolRegistry?.[operation] as any; + return tool?.name || t(`tools.${operation}.name`, operation); + }; + + const getAvailableTools = () => { + if (!toolRegistry) return []; + + return Object.entries(toolRegistry) + .filter(([key]) => key !== 'automate') // Don't allow recursive automations + .map(([key, tool]) => ({ + value: key, + label: (tool as any).name + })); + }; + + const addTool = (operation: string | null) => { + if (!operation) return; + + const newTool: AutomationTool = { + id: `${operation}-${Date.now()}`, + operation, + name: getToolName(operation), + configured: false, + parameters: {} + }; + + setSelectedTools([...selectedTools, newTool]); + }; + + const removeTool = (index: number) => { + setSelectedTools(selectedTools.filter((_, i) => i !== index)); + }; + + const configureTool = (index: number) => { + setConfiguringToolIndex(index); + setConfigModalOpen(true); + }; + + const handleToolConfigSave = (parameters: any) => { + if (configuraingToolIndex >= 0) { + const updatedTools = [...selectedTools]; + updatedTools[configuraingToolIndex] = { + ...updatedTools[configuraingToolIndex], + configured: true, + parameters + }; + setSelectedTools(updatedTools); + } + setConfigModalOpen(false); + setConfiguringToolIndex(-1); + }; + + const handleToolConfigCancel = () => { + setConfigModalOpen(false); + setConfiguringToolIndex(-1); + }; + + const canSaveAutomation = () => { + return ( + automationName.trim() !== '' && + selectedTools.length > 0 && + selectedTools.every(tool => tool.configured) + ); + }; + + const saveAutomation = async () => { + if (!canSaveAutomation()) return; + + const automation = { + name: automationName.trim(), + description: automationDescription.trim(), + operations: selectedTools.map(tool => ({ + operation: tool.operation, + parameters: tool.parameters + })) + }; + + try { + const { automationStorage } = await import('../../../services/automationStorage'); + await automationStorage.saveAutomation(automation); + onComplete(); + } catch (error) { + console.error('Error saving automation:', error); + // TODO: Show error notification to user + } + }; + + const currentConfigTool = configuraingToolIndex >= 0 ? selectedTools[configuraingToolIndex] : null; + + return ( + + +
+ + {mode === 'create' + ? t('automate.creation.title.create', 'Create New Automation') + : mode === 'suggested' + ? t('automate.creation.title.configure', 'Configure Automation') + : t('automate.creation.title.edit', 'Edit Automation') + } + + + {t('automate.creation.description', 'Add and configure tools to create your workflow')} + +
+ +
+ + {/* Automation Details */} + + + setAutomationName(e.currentTarget.value)} + required + /> +