mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-26 06:09:23 +00:00
add tools
This commit is contained in:
parent
a85ab4a832
commit
c6bd7a1bfc
@ -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<AutomationTool[]>([]);
|
||||
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 */}
|
||||
<ToolSelector
|
||||
onSelect={addTool}
|
||||
excludeTools={['automate']}
|
||||
toolRegistry={toolRegistry}
|
||||
/>
|
||||
|
||||
{/* Selected Tools */}
|
||||
{/* Selected Tools List */}
|
||||
{selectedTools.length > 0 && (
|
||||
<Stack gap="xs">
|
||||
{selectedTools.map((tool, index) => (
|
||||
<Group key={tool.id} gap="xs" align="center">
|
||||
<Text size="xs" c="dimmed" style={{ minWidth: '1rem', textAlign: 'center' }}>
|
||||
{index + 1}
|
||||
</Text>
|
||||
<div>
|
||||
<Text size="sm" fw={500} mb="xs" style={{ color: 'var(--mantine-color-text)' }}>
|
||||
{t('automate.creation.tools.selected', 'Selected Tools')} ({selectedTools.length})
|
||||
</Text>
|
||||
<Stack gap="0" style={{
|
||||
}}>
|
||||
{selectedTools.map((tool, index) => (
|
||||
<React.Fragment key={tool.id}>
|
||||
<div
|
||||
style={{
|
||||
border: '1px solid var(--mantine-color-gray-2)',
|
||||
borderRadius: 'var(--mantine-radius-sm)',
|
||||
backgroundColor: 'white'
|
||||
}}
|
||||
>
|
||||
<Group gap="xs" align="center" wrap="nowrap" style={{ width: '100%' }}>
|
||||
|
||||
<div style={{ flex: 1 }}>
|
||||
<Group justify="space-between" align="center">
|
||||
<Group gap="xs" align="center">
|
||||
<Text size="sm" style={{ color: 'var(--mantine-color-text)' }}>
|
||||
{tool.name}
|
||||
</Text>
|
||||
{tool.configured ? (
|
||||
<CheckIcon style={{ fontSize: 14, color: 'green' }} />
|
||||
) : (
|
||||
<CloseIcon style={{ fontSize: 14, color: 'orange' }} />
|
||||
)}
|
||||
</Group>
|
||||
<div style={{ flex: 1, minWidth: 0, overflow: 'hidden' }}>
|
||||
{/* Tool Selection Dropdown */}
|
||||
<ToolSelector
|
||||
key={`tool-selector-${tool.id}`}
|
||||
onSelect={(newOperation) => {
|
||||
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}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Group gap="xs">
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
size="sm"
|
||||
onClick={() => configureTool(index)}
|
||||
>
|
||||
<SettingsIcon style={{ fontSize: 16 }} />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
size="sm"
|
||||
color="red"
|
||||
onClick={() => removeTool(index)}
|
||||
>
|
||||
<DeleteIcon style={{ fontSize: 16 }} />
|
||||
</ActionIcon>
|
||||
<Group gap="xs" style={{ flexShrink: 0 }}>
|
||||
{tool.configured ? (
|
||||
<CheckIcon style={{ fontSize: 14, color: 'green' }} />
|
||||
) : (
|
||||
<CloseIcon style={{ fontSize: 14, color: 'orange' }} />
|
||||
)}
|
||||
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
size="sm"
|
||||
onClick={() => configureTool(index)}
|
||||
title={t('automate.creation.tools.configure', 'Configure tool')}
|
||||
>
|
||||
<SettingsIcon style={{ fontSize: 16 }} />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
size="sm"
|
||||
color="red"
|
||||
onClick={() => removeTool(index)}
|
||||
title={t('automate.creation.tools.remove', 'Remove tool')}
|
||||
>
|
||||
<DeleteIcon style={{ fontSize: 16 }} />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
</div>
|
||||
|
||||
{index < selectedTools.length - 1 && (
|
||||
<div style={{ textAlign: 'center', padding: '8px 0' }}>
|
||||
<Text size="xs" c="dimmed">↓</Text>
|
||||
</div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
|
||||
{/* Arrow before Add Tool Button */}
|
||||
{selectedTools.length > 0 && (
|
||||
<div style={{ textAlign: 'center', padding: '8px 0' }}>
|
||||
<Text size="xs" c="dimmed">↓</Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{index < selectedTools.length - 1 && (
|
||||
<Text size="xs" c="dimmed">→</Text>
|
||||
)}
|
||||
</Group>
|
||||
))}
|
||||
</Stack>
|
||||
{/* Add Tool Button */}
|
||||
<div style={{
|
||||
border: '1px solid var(--mantine-color-gray-2)',
|
||||
borderRadius: 'var(--mantine-radius-sm)',
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
<AutomationEntry
|
||||
title={t('automate.creation.tools.addTool', 'Add Tool')}
|
||||
badgeIcon={AddCircleOutline}
|
||||
operations={[]}
|
||||
onClick={() => {
|
||||
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}
|
||||
/>
|
||||
</div>
|
||||
</Stack>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Divider />
|
||||
@ -231,6 +337,28 @@ export default function AutomationCreation({ mode, existingAutomation, onBack, o
|
||||
onCancel={handleToolConfigCancel}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Unsaved Changes Warning Modal */}
|
||||
<Modal
|
||||
opened={unsavedWarningOpen}
|
||||
onClose={handleCancelBack}
|
||||
title={t('automate.creation.unsavedChanges.title', 'Unsaved Changes')}
|
||||
centered
|
||||
>
|
||||
<Stack gap="md">
|
||||
<Text>
|
||||
{t('automate.creation.unsavedChanges.message', 'You have unsaved changes. Are you sure you want to go back? All changes will be lost.')}
|
||||
</Text>
|
||||
<Group gap="md" justify="flex-end">
|
||||
<Button variant="outline" onClick={handleCancelBack}>
|
||||
{t('automate.creation.unsavedChanges.cancel', 'Cancel')}
|
||||
</Button>
|
||||
<Button color="red" onClick={handleConfirmBack}>
|
||||
{t('automate.creation.unsavedChanges.confirm', 'Go Back')}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -10,9 +10,17 @@ interface ToolSelectorProps {
|
||||
onSelect: (toolKey: string) => void;
|
||||
excludeTools?: string[];
|
||||
toolRegistry: Record<string, ToolRegistryEntry>; // 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 (
|
||||
<Menu
|
||||
opened={opened}
|
||||
@ -98,7 +114,8 @@ export default function ToolSelector({ onSelect, excludeTools = [], toolRegistry
|
||||
onChange={handleSearchChange}
|
||||
toolRegistry={filteredToolRegistry}
|
||||
mode="filter"
|
||||
placeholder={t('automate.creation.tools.add', 'Add a tool...')}
|
||||
placeholder={getDisplayValue()}
|
||||
hideIcon={true}
|
||||
/>
|
||||
</div>
|
||||
</Menu.Target>
|
||||
|
@ -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={<span className="material-symbols-rounded">search</span>}
|
||||
icon={hideIcon ? undefined : <span className="material-symbols-rounded">search</span>}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
|
@ -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
|
||||
},
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user