diff --git a/frontend/src/components/tools/automate/AutomationCreation.tsx b/frontend/src/components/tools/automate/AutomationCreation.tsx index c72c66036..e9f8de43f 100644 --- a/frontend/src/components/tools/automate/AutomationCreation.tsx +++ b/frontend/src/components/tools/automate/AutomationCreation.tsx @@ -8,15 +8,19 @@ import { Group, TextInput, ActionIcon, - Divider + Divider, + Modal } from '@mantine/core'; import DeleteIcon from '@mui/icons-material/Delete'; import SettingsIcon from '@mui/icons-material/Settings'; import CheckIcon from '@mui/icons-material/Check'; import CloseIcon from '@mui/icons-material/Close'; +import AddCircleOutline from '@mui/icons-material/AddCircleOutline'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import { ToolRegistryEntry } from '../../../data/toolsTaxonomy'; import ToolConfigurationModal from './ToolConfigurationModal'; import ToolSelector from './ToolSelector'; +import AutomationEntry from './AutomationEntry'; interface AutomationCreationProps { mode: 'custom' | 'suggested' | 'create'; @@ -41,6 +45,7 @@ export default function AutomationCreation({ mode, existingAutomation, onBack, o const [selectedTools, setSelectedTools] = useState([]); const [configModalOpen, setConfigModalOpen] = useState(false); const [configuraingToolIndex, setConfiguringToolIndex] = useState(-1); + const [unsavedWarningOpen, setUnsavedWarningOpen] = useState(false); // Initialize based on mode and existing automation useEffect(() => { @@ -56,8 +61,27 @@ export default function AutomationCreation({ mode, existingAutomation, onBack, o })); setSelectedTools(tools); + } else if (mode === 'create' && selectedTools.length === 0) { + // Initialize with 2 empty tools for new automation + const defaultTools = [ + { + id: `tool-1-${Date.now()}`, + operation: '', + name: t('automate.creation.tools.selectTool', 'Select a tool...'), + configured: false, + parameters: {} + }, + { + id: `tool-2-${Date.now() + 1}`, + operation: '', + name: t('automate.creation.tools.selectTool', 'Select a tool...'), + configured: false, + parameters: {} + } + ]; + setSelectedTools(defaultTools); } - }, [mode, existingAutomation]); + }, [mode, existingAutomation, selectedTools.length, t]); const getToolName = (operation: string) => { const tool = toolRegistry?.[operation] as any; @@ -78,6 +102,8 @@ export default function AutomationCreation({ mode, existingAutomation, onBack, o }; const removeTool = (index: number) => { + // Don't allow removing tools if only 2 remain + if (selectedTools.length <= 2) return; setSelectedTools(selectedTools.filter((_, i) => i !== index)); }; @@ -105,14 +131,38 @@ export default function AutomationCreation({ mode, existingAutomation, onBack, o setConfiguringToolIndex(-1); }; + const hasUnsavedChanges = () => { + return ( + automationName.trim() !== '' || + selectedTools.some(tool => tool.operation !== '' || tool.configured) + ); + }; + const canSaveAutomation = () => { return ( automationName.trim() !== '' && selectedTools.length > 0 && - selectedTools.every(tool => tool.configured) + selectedTools.every(tool => tool.configured && tool.operation !== '') ); }; + const handleBackClick = () => { + if (hasUnsavedChanges()) { + setUnsavedWarningOpen(true); + } else { + onBack(); + } + }; + + const handleConfirmBack = () => { + setUnsavedWarningOpen(false); + onBack(); + }; + + const handleCancelBack = () => { + setUnsavedWarningOpen(false); + }; + const saveAutomation = async () => { if (!canSaveAutomation()) return; @@ -152,61 +202,117 @@ export default function AutomationCreation({ mode, existingAutomation, onBack, o size="sm" /> - {/* Add Tool Selector */} - - {/* Selected Tools */} + {/* Selected Tools List */} {selectedTools.length > 0 && ( - - {selectedTools.map((tool, index) => ( - - - {index + 1} - +
+ + {t('automate.creation.tools.selected', 'Selected Tools')} ({selectedTools.length}) + + + {selectedTools.map((tool, index) => ( + +
+ -
- - - - {tool.name} - - {tool.configured ? ( - - ) : ( - - )} - +
+ {/* Tool Selection Dropdown */} + { + const updatedTools = [...selectedTools]; + updatedTools[index] = { + ...updatedTools[index], + operation: newOperation, + name: getToolName(newOperation), + configured: false, + parameters: {} + }; + setSelectedTools(updatedTools); + }} + excludeTools={['automate']} + toolRegistry={toolRegistry} + selectedValue={tool.operation} + placeholder={tool.name} + /> +
- - configureTool(index)} - > - - - removeTool(index)} - > - - + + {tool.configured ? ( + + ) : ( + + )} + + configureTool(index)} + title={t('automate.creation.tools.configure', 'Configure tool')} + > + + + + removeTool(index)} + title={t('automate.creation.tools.remove', 'Remove tool')} + > + + + -
+
+ + {index < selectedTools.length - 1 && ( +
+ +
+ )} + + ))} + + {/* Arrow before Add Tool Button */} + {selectedTools.length > 0 && ( +
+
+ )} - {index < selectedTools.length - 1 && ( - - )} -
- ))} - + {/* Add Tool Button */} +
+ { + const newTool: AutomationTool = { + id: `tool-${Date.now()}`, + operation: '', + name: t('automate.creation.tools.selectTool', 'Select a tool...'), + configured: false, + parameters: {} + }; + setSelectedTools([...selectedTools, newTool]); + }} + keepIconColor={true} + /> +
+ +
)} @@ -231,6 +337,28 @@ export default function AutomationCreation({ mode, existingAutomation, onBack, o onCancel={handleToolConfigCancel} /> )} + + {/* Unsaved Changes Warning Modal */} + + + + {t('automate.creation.unsavedChanges.message', 'You have unsaved changes. Are you sure you want to go back? All changes will be lost.')} + + + + + + +
); } diff --git a/frontend/src/components/tools/automate/ToolSelector.tsx b/frontend/src/components/tools/automate/ToolSelector.tsx index 8d49e4649..30515179b 100644 --- a/frontend/src/components/tools/automate/ToolSelector.tsx +++ b/frontend/src/components/tools/automate/ToolSelector.tsx @@ -10,9 +10,17 @@ interface ToolSelectorProps { onSelect: (toolKey: string) => void; excludeTools?: string[]; toolRegistry: Record; // Pass registry as prop to break circular dependency + selectedValue?: string; // For showing current selection when editing existing tool + placeholder?: string; // Custom placeholder text } -export default function ToolSelector({ onSelect, excludeTools = [], toolRegistry }: ToolSelectorProps) { +export default function ToolSelector({ + onSelect, + excludeTools = [], + toolRegistry, + selectedValue, + placeholder +}: ToolSelectorProps) { const { t } = useTranslation(); const [opened, setOpened] = useState(false); const [searchTerm, setSearchTerm] = useState(''); @@ -83,6 +91,14 @@ export default function ToolSelector({ onSelect, excludeTools = [], toolRegistry } }; + // Get display value for selected tool + const getDisplayValue = () => { + if (selectedValue && toolRegistry[selectedValue]) { + return toolRegistry[selectedValue].name; + } + return placeholder || t('automate.creation.tools.add', 'Add a tool...'); + }; + return ( diff --git a/frontend/src/components/tools/toolPicker/ToolSearch.tsx b/frontend/src/components/tools/toolPicker/ToolSearch.tsx index 516a40ad1..0cb86aa1e 100644 --- a/frontend/src/components/tools/toolPicker/ToolSearch.tsx +++ b/frontend/src/components/tools/toolPicker/ToolSearch.tsx @@ -13,6 +13,7 @@ interface ToolSearchProps { mode: 'filter' | 'dropdown'; selectedToolKey?: string | null; placeholder?: string; + hideIcon?: boolean; } const ToolSearch = ({ @@ -22,7 +23,8 @@ const ToolSearch = ({ onToolSelect, mode = 'filter', selectedToolKey, - placeholder + placeholder, + hideIcon = false }: ToolSearchProps) => { const { t } = useTranslation(); const [dropdownOpen, setDropdownOpen] = useState(false); @@ -64,7 +66,7 @@ const ToolSearch = ({ value={value} onChange={handleSearchChange} placeholder={placeholder || t("toolPicker.searchPlaceholder", "Search tools...")} - icon={search} + icon={hideIcon ? undefined : search} autoComplete="off" /> diff --git a/frontend/src/tools/Automate.tsx b/frontend/src/tools/Automate.tsx index 5c826c33b..bdbc7715e 100644 --- a/frontend/src/tools/Automate.tsx +++ b/frontend/src/tools/Automate.tsx @@ -81,6 +81,7 @@ const Automate = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => { { title: t('automate.stepTitle', 'Automations'), isVisible: true, + onCollapsedClick: ()=> setCurrentStep('selection'), content: currentStep === 'selection' ? renderCurrentStep() : null }, {