import { useState, useCallback, useRef, useEffect } from 'react' import { useTranslation } from 'react-i18next' import { Tool } from '@/types' import { ChevronDown, ChevronRight, Play, Loader, Edit, Check } from '@/components/icons/LucideIcons' import { callTool, ToolCallResult, updateToolDescription } from '@/services/toolService' import { useSettingsData } from '@/hooks/useSettingsData' import { Switch } from './ToggleGroup' import DynamicForm from './DynamicForm' import ToolResult from './ToolResult' interface ToolCardProps { server: string tool: Tool onToggle?: (toolName: string, enabled: boolean) => void onDescriptionUpdate?: (toolName: string, description: string) => void } // Helper to check for "empty" values function isEmptyValue(value: any): boolean { if (value == null) return true; // null or undefined if (typeof value === 'string') return value.trim() === ''; if (Array.isArray(value)) return value.length === 0; if (typeof value === 'object') return Object.keys(value).length === 0; return false; } const ToolCard = ({ tool, server, onToggle, onDescriptionUpdate }: ToolCardProps) => { const { t } = useTranslation() const { nameSeparator } = useSettingsData() const [isExpanded, setIsExpanded] = useState(false) const [showRunForm, setShowRunForm] = useState(false) const [isRunning, setIsRunning] = useState(false) const [result, setResult] = useState(null) const [isEditingDescription, setIsEditingDescription] = useState(false) const [customDescription, setCustomDescription] = useState(tool.description || '') const descriptionInputRef = useRef(null) const descriptionTextRef = useRef(null) const [textWidth, setTextWidth] = useState(0) // Focus the input when editing mode is activated useEffect(() => { if (isEditingDescription && descriptionInputRef.current) { descriptionInputRef.current.focus() // Set input width to match text width if (textWidth > 0) { descriptionInputRef.current.style.width = `${textWidth + 20}px` // Add some padding } } }, [isEditingDescription, textWidth]) // Measure text width when not editing useEffect(() => { if (!isEditingDescription && descriptionTextRef.current) { setTextWidth(descriptionTextRef.current.offsetWidth) } }, [isEditingDescription, customDescription]) // Generate a unique key for localStorage based on tool name and server const getStorageKey = useCallback(() => { return `mcphub_tool_form_${server ? `${server}_` : ''}${tool.name}` }, [tool.name, server]) // Clear form data from localStorage const clearStoredFormData = useCallback(() => { localStorage.removeItem(getStorageKey()) }, [getStorageKey]) const handleToggle = (enabled: boolean) => { if (onToggle) { onToggle(tool.name, enabled) } } const handleDescriptionEdit = () => { setIsEditingDescription(true) } const handleDescriptionSave = async () => { try { const result = await updateToolDescription(server, tool.name, customDescription) if (result.success) { setIsEditingDescription(false) if (onDescriptionUpdate) { onDescriptionUpdate(tool.name, customDescription) } } else { // Revert on error setCustomDescription(tool.description || '') console.error('Failed to update tool description:', result.error) } } catch (error) { console.error('Error updating tool description:', error) setCustomDescription(tool.description || '') setIsEditingDescription(false) } } const handleDescriptionChange = (e: React.ChangeEvent) => { setCustomDescription(e.target.value) } const handleDescriptionKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { handleDescriptionSave() } else if (e.key === 'Escape') { setCustomDescription(tool.description || '') setIsEditingDescription(false) } } const handleRunTool = async (arguments_: Record) => { setIsRunning(true) try { // filter empty values arguments_ = Object.fromEntries(Object.entries(arguments_).filter(([_, v]) => !isEmptyValue(v))) const result = await callTool({ toolName: tool.name, arguments: arguments_, }, server) setResult(result) // Clear form data on successful submission // clearStoredFormData() } catch (error) { setResult({ success: false, error: error instanceof Error ? error.message : 'Unknown error occurred', }) } finally { setIsRunning(false) } } const handleCancelRun = () => { setShowRunForm(false) // Clear form data when cancelled clearStoredFormData() setResult(null) } const handleCloseResult = () => { setResult(null) } return (
setIsExpanded(!isExpanded)} >

{tool.name.replace(server + nameSeparator, '')} {isEditingDescription ? ( <> e.stopPropagation()} style={{ minWidth: '100px', width: textWidth > 0 ? `${textWidth + 20}px` : 'auto' }} /> ) : ( <> {customDescription || t('tool.noDescription')} )}

e.stopPropagation()} >
{isExpanded && (
{/* Schema Display */} {!showRunForm && (

{t('tool.inputSchema')}

                {JSON.stringify(tool.inputSchema, null, 2)}
              
)} {/* Run Form */} {showRunForm && (
{/* Tool Result */} {result && (
)}
)}
)}
) } export default ToolCard