Skip to main content
Glama
jmandel

Smart EHR MCP Server

by jmandel
App.tsx8.33 kB
import React, { useState, useCallback, useMemo } from 'react'; import { useTaskLiaison } from './hooks/useTaskLiaison'; import { Message, TextPart, Task, DataPart } from '@jmandel/a2a-client/src/types'; // A2A types import './App.css'; // Keep default styling for now const JOKE_AGENT_URL = 'http://localhost:3100/a2a'; // Make sure your sample agent runs here function App() { const [inputValue, setInputValue] = useState(''); // Custom summary logic for the Joke agent const jokeSummaryGenerator = useCallback((task: Task | null) => { if (!task) return { label: 'Idle', icon: '😴' }; switch (task.status.state) { case 'submitted': case 'working': return { label: 'Thinking of a joke...', icon: '⏳' }; case 'input-required': return { label: 'What kind of joke?', icon: '❓' }; case 'completed': const jokeText = (task.artifacts?.[0]?.parts?.[0] as TextPart)?.text || '(Joke received!)'; return { label: 'Joke:', detail: jokeText, icon: '😂' }; case 'canceled': return { label: 'Joke canceled', icon: '❌' }; case 'failed': return { label: 'Failed to get joke', icon: '🔥' }; default: return { label: `Status: ${task.status.state}` }; } }, []); const { task: _task, summary, questionForUser, clientStatus, error, startTask, sendInput, cancelTask, taskId } = useTaskLiaison({ agentUrl: JOKE_AGENT_URL, summaryGenerator: jokeSummaryGenerator, }); // Define isRunning *before* the callbacks that use it const isRunning = clientStatus !== 'idle' && clientStatus !== 'completed' && clientStatus !== 'error'; // --- App-specific logic to interpret the questionForUser Message --- const promptText = useMemo(() => { if (!questionForUser) return '\u00A0'; // Return non-breaking space if no question // Find the first text part in the message const textPart = questionForUser.parts.find(p => p.type === 'text') as TextPart | undefined; return textPart?.text || 'Input required'; // Fallback text }, [questionForUser]); const currentOptions = useMemo(() => { if (!questionForUser) return null; // Find the first data part const dataPart = questionForUser.parts.find(p => p.type === 'data') as DataPart | undefined; // Check if it has an 'options' array if (dataPart?.data && Array.isArray(dataPart.data.options)) { return dataPart.data.options as string[]; } return null; }, [questionForUser]); // --- End App-specific logic --- // Generic submission handler - now constructs the Message const handleSubmit = useCallback((valueToSubmit?: string) => { const finalValue = valueToSubmit ?? inputValue; if (!finalValue || clientStatus !== 'awaiting-input') return; console.log(`Constructing message for value: ${finalValue}`); // Construct the standard message structure here const message: Message = { role: 'user', parts: [{ type: 'text', text: finalValue }] // If App needed to send other parts, logic would go here }; sendInput(message) // Call the hook's sendInput .then(() => setInputValue('')) // Clear input on success .catch(console.error); }, [sendInput, inputValue, clientStatus]); // Use sendInput dependency // Handler for input changes - includes auto-submit logic specific to this App const handleInputChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => { const newValue = event.target.value; setInputValue(newValue); if (currentOptions && currentOptions.includes(newValue)) { console.log(`Auto-submitting selected option: ${newValue}`); handleSubmit(newValue); // Pass the string value to handleSubmit } }, [currentOptions, handleSubmit]); // Handler for Enter key press const handleKeyDown = useCallback((event: React.KeyboardEvent<HTMLInputElement>) => { if (event.key === 'Enter' && clientStatus === 'awaiting-input' && isRunning && inputValue) { event.preventDefault(); console.log("Enter key pressed, submitting value..."); handleSubmit(); // Calls handleSubmit with current inputValue } }, [clientStatus, isRunning, inputValue, handleSubmit]); const handleStart = useCallback(() => { setInputValue(''); console.log("Starting joke task..."); // Start with a generic message, agent determines if input is needed startTask({ message: { role: 'user', parts: [{ type: 'text', text: 'Tell me a joke' }] } }) .catch(console.error); }, [startTask]); const handleCancel = useCallback(() => { cancelTask().catch(console.error); }, [cancelTask]); return ( <div className="App"> <h1>A4A React Hook Demo</h1> <p>Agent URL: <code>{JOKE_AGENT_URL}</code></p> <div className="card"> <div> <h2>Task Liaison</h2> <div className="status-display"> <p> Status: <strong>{summary.label}</strong> {summary.icon} <br/> <small>(Client: {clientStatus} {taskId ? `| Task: ${taskId}` : ''})</small> </p> {summary.detail && ( <p className="summary-detail"> {summary.detail} </p> )} </div> {error && <p className="error-message">Error: {error.message}</p>} </div> <div className="controls-wrapper"> <div className="controls-area"> {clientStatus === 'idle' && ( <button onClick={handleStart}>Tell me a joke</button> )} {isRunning && clientStatus !== 'awaiting-input' && ( <button onClick={handleCancel} className="cancel-button"> Cancel Task </button> )} </div> </div> <div className={`input-section ${clientStatus === 'awaiting-input' && questionForUser ? 'active' : ''}`}> <label htmlFor="genericInput"> {promptText} </label> <input id="genericInput" type="text" value={inputValue} onChange={handleInputChange} onKeyDown={handleKeyDown} placeholder={promptText.trim() || "Enter input"} disabled={clientStatus !== 'awaiting-input' || !isRunning} list={currentOptions ? "inputOptions" : undefined} /> {currentOptions && ( <datalist id="inputOptions"> {currentOptions.map((option) => ( <option key={option} value={option} /> ))} </datalist> )} <button onClick={() => handleSubmit()} disabled={clientStatus !== 'awaiting-input' || !isRunning || !inputValue} > Send </button> </div> </div> {/* Optional: Display raw task state for debugging */} {/* <div className="card"> <h2>Raw Task State</h2> <pre style={{ textAlign: 'left', background: '#f8f8f8', padding: '10px', borderRadius: '5px', maxHeight: '300px', overflowY: 'auto' }}> {JSON.stringify(task, null, 2) || 'No task data'} </pre> </div> */} </div> ); } export default App;

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jmandel/health-record-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server