@@ -4,84 +4,39 @@ import { callTool, connectToServer, hasAppHtml, initializeApp, loadSandboxProxy,
44import styles from "./index.module.css" ;
55
66
7- // Wrapper to track server name with each tool call
8- interface ToolCallEntry {
9- serverName : string ;
10- info : ToolCallInfo ;
11- }
12-
13- // Host receives connected servers via promise, uses single use() call
7+ // Host passes serversPromise to CallToolPanel
148interface HostProps {
159 serversPromise : Promise < ServerInfo [ ] > ;
1610}
1711function Host ( { serversPromise } : HostProps ) {
18- const servers = use ( serversPromise ) ;
19- const [ toolCalls , setToolCalls ] = useState < ToolCallEntry [ ] > ( [ ] ) ;
20-
21- if ( servers . length === 0 ) {
22- return < p > No servers configured. Set SERVERS environment variable.</ p > ;
23- }
12+ const [ toolCalls , setToolCalls ] = useState < ToolCallInfo [ ] > ( [ ] ) ;
2413
2514 return (
2615 < >
27- { toolCalls . map ( ( entry , i ) => (
28- < ToolCallInfoPanel key = { i } serverName = { entry . serverName } toolCallInfo = { entry . info } />
16+ { toolCalls . map ( ( info , i ) => (
17+ < ToolCallInfoPanel key = { i } toolCallInfo = { info } />
2918 ) ) }
3019 < CallToolPanel
31- servers = { servers }
32- addToolCall = { ( serverName , info ) => setToolCalls ( [ ...toolCalls , { serverName , info } ] ) }
20+ serversPromise = { serversPromise }
21+ addToolCall = { ( info ) => setToolCalls ( [ ...toolCalls , info ] ) }
3322 />
3423 </ >
3524 ) ;
3625}
3726
3827
39- // CallToolPanel manages server selection from already-connected servers
28+ // CallToolPanel renders the unified form with Suspense around ServerSelect
4029interface CallToolPanelProps {
41- servers : ServerInfo [ ] ;
42- addToolCall : ( serverName : string , info : ToolCallInfo ) => void ;
43- }
44- function CallToolPanel ( { servers, addToolCall } : CallToolPanelProps ) {
45- const [ selectedIndex , setSelectedIndex ] = useState ( 0 ) ;
46- const selectedServer = servers [ selectedIndex ] ;
47-
48- return (
49- < div className = { styles . callToolPanel } >
50- < label >
51- Server
52- < select
53- value = { selectedIndex }
54- onChange = { ( e ) => setSelectedIndex ( Number ( e . target . value ) ) }
55- >
56- { servers . map ( ( server , i ) => (
57- < option key = { i } value = { i } >
58- { server . name }
59- </ option >
60- ) ) }
61- </ select >
62- </ label >
63- < ToolCallForm
64- key = { selectedIndex }
65- serverName = { selectedServer . name }
66- serverInfo = { selectedServer }
67- addToolCall = { addToolCall }
68- />
69- </ div >
70- ) ;
71- }
72-
73-
74- // ToolCallForm receives already-resolved serverInfo
75- interface ToolCallFormProps {
76- serverName : string ;
77- serverInfo : ServerInfo ;
78- addToolCall : ( serverName : string , info : ToolCallInfo ) => void ;
30+ serversPromise : Promise < ServerInfo [ ] > ;
31+ addToolCall : ( info : ToolCallInfo ) => void ;
7932}
80- function ToolCallForm ( { serverName , serverInfo , addToolCall } : ToolCallFormProps ) {
81- const toolNames = Array . from ( serverInfo . tools . keys ( ) ) ;
82- const [ selectedTool , setSelectedTool ] = useState ( toolNames [ 0 ] ?? "" ) ;
33+ function CallToolPanel ( { serversPromise , addToolCall } : CallToolPanelProps ) {
34+ const [ selectedServer , setSelectedServer ] = useState < ServerInfo | null > ( null ) ;
35+ const [ selectedTool , setSelectedTool ] = useState ( "" ) ;
8336 const [ inputJson , setInputJson ] = useState ( "{}" ) ;
8437
38+ const toolNames = selectedServer ? Array . from ( selectedServer . tools . keys ( ) ) : [ ] ;
39+
8540 const isValidJson = useMemo ( ( ) => {
8641 try {
8742 JSON . parse ( inputJson ) ;
@@ -91,49 +46,104 @@ function ToolCallForm({ serverName, serverInfo, addToolCall }: ToolCallFormProps
9146 }
9247 } , [ inputJson ] ) ;
9348
49+ const handleServerSelect = ( server : ServerInfo ) => {
50+ setSelectedServer ( server ) ;
51+ const [ firstTool ] = server . tools . keys ( ) ;
52+ setSelectedTool ( firstTool ?? "" ) ;
53+ } ;
54+
9455 const handleSubmit = ( ) => {
95- const toolCallInfo = callTool ( serverInfo , selectedTool , JSON . parse ( inputJson ) ) ;
96- addToolCall ( serverName , toolCallInfo ) ;
56+ if ( ! selectedServer ) return ;
57+ const toolCallInfo = callTool ( selectedServer , selectedTool , JSON . parse ( inputJson ) ) ;
58+ addToolCall ( toolCallInfo ) ;
9759 } ;
9860
9961 return (
100- < form onSubmit = { ( e ) => { e . preventDefault ( ) ; handleSubmit ( ) ; } } >
101- < label >
102- Tool
103- < select
104- value = { selectedTool }
105- onChange = { ( e ) => setSelectedTool ( e . target . value ) }
106- >
107- { toolNames . map ( ( name ) => (
108- < option key = { name } value = { name } > { name } </ option >
109- ) ) }
110- </ select >
111- </ label >
112- < label >
113- Input
114- < textarea
115- aria-invalid = { ! isValidJson }
116- value = { inputJson }
117- onChange = { ( e ) => setInputJson ( e . target . value ) }
118- />
119- </ label >
120- < button type = "submit" disabled = { ! selectedTool || ! isValidJson } >
121- Call Tool
122- </ button >
123- </ form >
62+ < div className = { styles . callToolPanel } >
63+ < form onSubmit = { ( e ) => { e . preventDefault ( ) ; handleSubmit ( ) ; } } >
64+ < label >
65+ Server
66+ < Suspense fallback = { < select disabled > < option > Loading...</ option > </ select > } >
67+ < ServerSelect serversPromise = { serversPromise } onSelect = { handleServerSelect } />
68+ </ Suspense >
69+ </ label >
70+ < label >
71+ Tool
72+ < select
73+ className = { styles . toolSelect }
74+ value = { selectedTool }
75+ onChange = { ( e ) => setSelectedTool ( e . target . value ) }
76+ >
77+ { selectedServer && toolNames . map ( ( name ) => (
78+ < option key = { name } value = { name } > { name } </ option >
79+ ) ) }
80+ </ select >
81+ </ label >
82+ < label >
83+ Input
84+ < textarea
85+ className = { styles . toolInput }
86+ aria-invalid = { ! isValidJson }
87+ value = { inputJson }
88+ onChange = { ( e ) => setInputJson ( e . target . value ) }
89+ />
90+ </ label >
91+ < button type = "submit" disabled = { ! selectedTool || ! isValidJson } >
92+ Call Tool
93+ </ button >
94+ </ form >
95+ </ div >
96+ ) ;
97+ }
98+
99+
100+ // ServerSelect calls use() and renders the server <select>
101+ interface ServerSelectProps {
102+ serversPromise : Promise < ServerInfo [ ] > ;
103+ onSelect : ( server : ServerInfo ) => void ;
104+ }
105+ function ServerSelect ( { serversPromise, onSelect } : ServerSelectProps ) {
106+ const servers = use ( serversPromise ) ;
107+ const [ selectedIndex , setSelectedIndex ] = useState ( 0 ) ;
108+
109+ useEffect ( ( ) => {
110+ if ( servers . length > selectedIndex ) {
111+ onSelect ( servers [ selectedIndex ] ) ;
112+ }
113+ } , [ servers ] ) ;
114+
115+ if ( servers . length === 0 ) {
116+ return < select disabled > < option > No servers configured</ option > </ select > ;
117+ }
118+
119+ return (
120+ < select
121+ value = { selectedIndex }
122+ onChange = { ( e ) => {
123+ const newIndex = Number ( e . target . value ) ;
124+ setSelectedIndex ( newIndex ) ;
125+ onSelect ( servers [ newIndex ] ) ;
126+ } }
127+ >
128+ { servers . map ( ( server , i ) => (
129+ < option key = { i } value = { i } > { server . name } </ option >
130+ ) ) }
131+ </ select >
124132 ) ;
125133}
126134
127135
128136interface ToolCallInfoPanelProps {
129- serverName : string ;
130137 toolCallInfo : ToolCallInfo ;
131138}
132- function ToolCallInfoPanel ( { serverName , toolCallInfo } : ToolCallInfoPanelProps ) {
139+ function ToolCallInfoPanel ( { toolCallInfo } : ToolCallInfoPanelProps ) {
133140 return (
134141 < div className = { styles . toolCallInfoPanel } >
135142 < div className = { styles . inputInfoPanel } >
136- < h2 className = { styles . toolName } > { serverName } :{ toolCallInfo . tool . name } </ h2 >
143+ < h2 >
144+ < span > { toolCallInfo . serverInfo . name } </ span >
145+ < span className = { styles . toolName } > { toolCallInfo . tool . name } </ span >
146+ </ h2 >
137147 < JsonBlock value = { toolCallInfo . input } />
138148 </ div >
139149 < div className = { styles . outputInfoPanel } >
@@ -239,9 +249,7 @@ async function connectToAllServers(): Promise<ServerInfo[]> {
239249createRoot ( document . getElementById ( "root" ) ! ) . render (
240250 < StrictMode >
241251 < ErrorBoundary >
242- < Suspense fallback = { < p className = { styles . connecting } > Connecting to servers...</ p > } >
243- < Host serversPromise = { connectToAllServers ( ) } />
244- </ Suspense >
252+ < Host serversPromise = { connectToAllServers ( ) } />
245253 </ ErrorBoundary >
246254 </ StrictMode > ,
247255) ;
0 commit comments