AgentIQ_Frontend/src/components/ui/add-tool-dialog.tsx

269 lines
13 KiB
TypeScript

/**
* Add Tool Dialog Component
* @description Dialog for adding and configuring tools (API/MCP)
*/
import { useState, useRef, useEffect } from 'react';
import { ChevronDown } from 'lucide-react';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogClose,
} from './dialog';
import type { Tool } from '@/types';
interface AddToolDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onAddTool?: (tool: Omit<Tool, 'id'>) => void;
onUpdateTool?: (toolId: string, tool: Omit<Tool, 'id'>) => void;
editingTool?: Tool | null;
}
// Tool options for the dropdown with their display names and colors
const toolOptions = [
{ name: 'Slack', displayName: 'Slack', color: '#ff0080' },
{ name: 'GitHub', displayName: 'GitHub', color: '#8fbc24' },
{ name: 'Search', displayName: 'Search', color: '#0033FF' },
{ name: 'Weather', displayName: 'Weather', color: '#03f' },
{ name: 'Calculator', displayName: 'Calculator', color: '#03f' },
{ name: 'Translator', displayName: 'Translator', color: '#03f' },
];
/**
* AddToolDialog component
* @description Modal dialog for adding tools with API/MCP selection
* @param open - Whether the dialog is open
* @param onOpenChange - Callback when dialog open state changes
*/
export function AddToolDialog({ open, onOpenChange, onAddTool, onUpdateTool, editingTool }: AddToolDialogProps) {
const [selectedType, setSelectedType] = useState<'API' | 'MCP'>('API');
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const [selectedTool, setSelectedTool] = useState<typeof toolOptions[0] | null>(null);
const dropdownRef = useRef<HTMLDivElement>(null);
const selectRef = useRef<HTMLDivElement>(null);
// Initialize form when editing a tool
useEffect(() => {
if (editingTool && open) {
setSelectedType(editingTool.type);
const toolOption = toolOptions.find((opt) => opt.displayName === editingTool.name);
if (toolOption) {
setSelectedTool(toolOption);
}
} else if (!editingTool && open) {
// Reset when adding new tool
setSelectedType('API');
setSelectedTool(null);
}
}, [editingTool, open]);
// Close dropdown when clicking outside
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
dropdownRef.current &&
selectRef.current &&
!dropdownRef.current.contains(event.target as Node) &&
!selectRef.current.contains(event.target as Node)
) {
setIsDropdownOpen(false);
}
};
if (isDropdownOpen) {
document.addEventListener('mousedown', handleClickOutside);
}
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [isDropdownOpen]);
// Reset state when dialog closes
useEffect(() => {
if (!open) {
setIsDropdownOpen(false);
setSelectedTool(null);
}
}, [open]);
const handleToolSelect = (tool: typeof toolOptions[0]) => {
setSelectedTool(tool);
setIsDropdownOpen(false);
};
const handleSave = () => {
if (selectedTool) {
const toolData = {
name: selectedTool.displayName,
type: selectedType,
color: selectedTool.color,
};
if (editingTool && onUpdateTool) {
// Update existing tool
onUpdateTool(editingTool.id, toolData);
} else if (onAddTool) {
// Add new tool
onAddTool(toolData);
}
// Reset state
setSelectedTool(null);
setIsDropdownOpen(false);
onOpenChange(false);
}
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="w-[720px]">
<DialogHeader>
<div className="flex flex-col gap-0.5">
<DialogTitle className="font-['Poppins:SemiBold',sans-serif] text-[22px] text-black leading-normal">
{editingTool ? 'Update Tool' : 'Add Tool'}
</DialogTitle>
<DialogDescription className="font-['Poppins:Regular',sans-serif] text-base text-[rgba(0,0,0,0.75)]">
Quick easy smart configuration utility
</DialogDescription>
</div>
<DialogClose onClick={() => onOpenChange(false)} />
</DialogHeader>
<div className="px-10 pt-6 pb-8">
<div className="flex flex-col gap-6">
{/* API/MCP Radio Button Selection */}
<div className="flex gap-4 items-start">
{/* API Radio Button */}
<label className="flex gap-1 items-center cursor-pointer group">
<input
type="radio"
name="toolType"
value="API"
checked={selectedType === 'API'}
onChange={() => setSelectedType('API')}
className="sr-only"
/>
<div className="relative size-5 flex items-center justify-center">
<div
className={`relative size-5 rounded-full border-2 flex items-center justify-center transition-all ${
selectedType === 'API'
? 'border-[#0033FF]'
: 'border-[#E5E8F4]'
}`}
>
{selectedType === 'API' && (
<div className="size-2.5 rounded-full bg-[#0033FF]" />
)}
</div>
</div>
<span className="font-['Poppins:Medium',sans-serif] text-sm text-black leading-[1.6] select-none">
API
</span>
</label>
{/* MCP Radio Button */}
<label className="flex gap-1 items-center cursor-pointer group">
<input
type="radio"
name="toolType"
value="MCP"
checked={selectedType === 'MCP'}
onChange={() => setSelectedType('MCP')}
className="sr-only"
/>
<div className="relative size-5 flex items-center justify-center">
<div
className={`relative size-5 rounded-full border-2 flex items-center justify-center transition-all ${
selectedType === 'MCP'
? 'border-[#0033FF]'
: 'border-[#E5E8F4]'
}`}
>
{selectedType === 'MCP' && (
<div className="size-2.5 rounded-full bg-[#0033FF]" />
)}
</div>
</div>
<span className="font-['Poppins:Medium',sans-serif] text-sm text-black leading-[1.6] select-none">
MCP
</span>
</label>
</div>
{/* Configure Tools Dropdown */}
<div className="flex flex-col gap-2.5 items-start relative w-full">
<label className="font-['Poppins:Medium',sans-serif] text-sm text-black leading-[1.6]">
Configure tools
</label>
<div className="relative w-full">
<div
ref={selectRef}
onClick={() => setIsDropdownOpen(!isDropdownOpen)}
className={`bg-white border border-solid flex gap-3 h-[46px] items-center justify-between px-4 py-[15px] rounded-tl-[8px] rounded-tr-[8px] shadow-[0px_4px_8px_0px_#EEF1F7] w-full cursor-pointer transition-colors ${
isDropdownOpen
? 'border-[#0033FF] shadow-[0px_4px_8px_0px_rgba(0,51,255,0.25)]'
: 'border-[#E5E8F4] hover:border-[#0033FF]'
}`}
>
<span className="flex-1 font-['Poppins:Regular',sans-serif] text-sm text-black text-left tracking-[0.2px]">
{selectedTool?.displayName || 'Select'}
</span>
<div className="flex items-center justify-center relative shrink-0">
<ChevronDown
className={`size-5 transition-transform ${isDropdownOpen ? 'rotate-180' : ''}`}
/>
</div>
</div>
{/* Dropdown Menu */}
{isDropdownOpen && (
<div
ref={dropdownRef}
className="absolute bg-white border border-[#0033FF] border-solid flex flex-col items-start left-0 rounded-bl-[8px] rounded-br-[8px] shadow-[0px_4px_8px_0px_#EEF1F7] top-[46px] w-full z-10"
>
{toolOptions.map((tool, index) => (
<button
key={index}
type="button"
onClick={() => handleToolSelect(tool)}
className={`w-full px-4 py-[13px] text-left font-['Poppins:Regular',sans-serif] text-sm leading-[20px] transition-colors border-b border-[#E5E8F4] last:border-b-0 ${
selectedTool?.name === tool.name
? 'bg-[rgba(0,51,255,0.1)] text-[#0033FF]'
: 'text-black hover:bg-[rgba(0,51,255,0.05)]'
}`}
>
{tool.displayName}
</button>
))}
</div>
)}
</div>
</div>
{/* Save Button */}
<div className="flex h-[46px] items-center justify-center w-full">
<button
type="button"
onClick={handleSave}
disabled={!selectedTool}
className="bg-[#03f] flex flex-1 items-center justify-center px-[42px] py-[14px] rounded-[12px] hover:bg-[#002BCC] transition-colors cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
>
<span className="font-['Poppins:SemiBold',sans-serif] leading-[18px] not-italic text-[16px] text-center text-white">
{editingTool ? 'Update' : 'Save'}
</span>
</button>
</div>
</div>
</div>
</DialogContent>
</Dialog>
);
}