269 lines
13 KiB
TypeScript
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>
|
|
);
|
|
}
|
|
|