import { useState, useCallback, useRef, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { Prompt } from '@/types'; import { ChevronDown, ChevronRight, Play, Loader, Edit, Check, } from '@/components/icons/LucideIcons'; import { Switch } from './ToggleGroup'; import { getPrompt, updatePromptDescription, PromptCallResult } from '@/services/promptService'; import { useSettingsData } from '@/hooks/useSettingsData'; import DynamicForm from './DynamicForm'; import PromptResult from './PromptResult'; import { useToast } from '@/contexts/ToastContext'; interface PromptCardProps { server: string; prompt: Prompt; onToggle?: (promptName: string, enabled: boolean) => void; onDescriptionUpdate?: (promptName: string, description: string) => void; } const PromptCard = ({ prompt, server, onToggle, onDescriptionUpdate }: PromptCardProps) => { const { t } = useTranslation(); const { showToast } = useToast(); 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(prompt.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 prompt name and server const getStorageKey = useCallback(() => { return `mcphub_prompt_form_${server ? `${server}_` : ''}${prompt.name}`; }, [prompt.name, server]); // Clear form data from localStorage const clearStoredFormData = useCallback(() => { localStorage.removeItem(getStorageKey()); }, [getStorageKey]); const handleToggle = (enabled: boolean) => { if (onToggle) { onToggle(prompt.name, enabled); } }; const handleDescriptionEdit = () => { setIsEditingDescription(true); }; const handleDescriptionSave = async () => { setIsEditingDescription(false); try { const result = await updatePromptDescription(server, prompt.name, customDescription); if (result.success) { showToast(t('prompt.descriptionUpdateSuccess'), 'success'); if (onDescriptionUpdate) { onDescriptionUpdate(prompt.name, customDescription); } } else { showToast(result.error || t('prompt.descriptionUpdateFailed'), 'error'); // Revert to original description on failure setCustomDescription(prompt.description || ''); } } catch (error) { console.error('Error updating prompt description:', error); showToast(t('prompt.descriptionUpdateFailed'), 'error'); // Revert to original description on failure setCustomDescription(prompt.description || ''); } }; 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(prompt.description || ''); setIsEditingDescription(false); } }; const handleGetPrompt = async (arguments_: Record) => { setIsRunning(true); try { const result = await getPrompt({ promptName: prompt.name, arguments: arguments_ }, server); console.log('GetPrompt result:', result); setResult({ success: result.success, data: result.data, error: result.error, }); // 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); }; // Convert prompt arguments to ToolInputSchema format for DynamicForm const convertToSchema = () => { if (!prompt.arguments || prompt.arguments.length === 0) { return { type: 'object', properties: {}, required: [] }; } const properties: Record = {}; const required: string[] = []; prompt.arguments.forEach((arg) => { properties[arg.name] = { type: 'string', // Default to string for prompts description: arg.description || '', }; if (arg.required) { required.push(arg.name); } }); return { type: 'object', properties, required, }; }; return (
setIsExpanded(!isExpanded)} >

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

e.stopPropagation()}> {prompt.enabled !== undefined && ( )}
{isExpanded && (
{/* Run Form */} {showRunForm && (
{/* Prompt Result */} {result && (
)}
)} {/* Arguments Display (when not showing form) */} {!showRunForm && prompt.arguments && prompt.arguments.length > 0 && (

{t('tool.parameters')}

{prompt.arguments.map((arg, index) => (
{arg.name} {arg.required && *}
{arg.description && (

{arg.description}

)}
{arg.title || ''}
))}
)} {/* Result Display (when not showing form) */} {!showRunForm && result && (
)}
)}
); }; export default PromptCard;