Skip to content

Commit 4064f67

Browse files
committed
feat(basic-host): Unified tool call layout with shared collapsible panels
1 parent 412bb47 commit 4064f67

2 files changed

Lines changed: 38 additions & 126 deletions

File tree

examples/basic-host/src/index.module.css

Lines changed: 3 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -72,65 +72,15 @@
7272

7373
.toolCallInfoPanel {
7474
display: flex;
75-
gap: 1rem;
75+
flex-direction: column;
76+
gap: 0.5rem;
7677
animation: slideDown 0.3s ease-out;
7778
}
7879

7980
@keyframes slideDown {
8081
from { opacity: 0; transform: translateY(-12px); }
8182
}
8283

83-
.inputInfoPanel {
84-
display: flex;
85-
flex: 3;
86-
flex-direction: column;
87-
gap: 0.5rem;
88-
min-width: 0;
89-
90-
h2 {
91-
display: flex;
92-
flex-direction: column;
93-
margin: 0;
94-
font-size: 1.5rem;
95-
position: relative;
96-
97-
.toolName {
98-
font-family: monospace;
99-
}
100-
101-
.closeButton {
102-
position: absolute;
103-
top: 0;
104-
right: 0;
105-
width: 1.5rem;
106-
height: 1.5rem;
107-
padding: 0;
108-
border: none;
109-
border-radius: 4px;
110-
background: #e0e0e0;
111-
font-size: 1.25rem;
112-
line-height: 1;
113-
color: #666;
114-
cursor: pointer;
115-
116-
&:hover {
117-
background: #d0d0d0;
118-
color: #333;
119-
}
120-
}
121-
}
122-
}
123-
124-
.outputInfoPanel {
125-
flex: 4;
126-
min-width: 0;
127-
}
128-
129-
.appOutputPanel {
130-
flex: 1;
131-
min-width: 0;
132-
}
133-
13484
.appHeader {
13585
display: flex;
13686
align-items: center;
@@ -166,16 +116,6 @@
166116
}
167117
}
168118

169-
.jsonBlock {
170-
flex-grow: 1;
171-
min-height: 0;
172-
margin: 0;
173-
padding: 1rem;
174-
border-radius: 4px;
175-
background-color: #f5f5f5;
176-
overflow: auto;
177-
}
178-
179119
.appIframePanel {
180120
min-height: 200px;
181121

@@ -196,6 +136,7 @@
196136
z-index: 1000;
197137
margin: 0;
198138
padding: 1rem;
139+
max-width: none;
199140
background: white;
200141
border: none;
201142
border-radius: 0;

examples/basic-host/src/index.tsx

Lines changed: 35 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -307,70 +307,50 @@ function ToolCallInfoPanel({ toolCallInfo, isDestroying, onRequestClose, onClose
307307
}
308308
}, [isDestroying, isApp, onCloseComplete]);
309309

310+
const inputJson = JSON.stringify(toolCallInfo.input, null, 2);
311+
310312
return (
311313
<div
312314
className={styles.toolCallInfoPanel}
313315
style={isDestroying ? { opacity: 0.5, pointerEvents: "none" } : undefined}
314316
>
315-
{/* For non-app tools, show input/output side by side */}
316-
{!isApp && (
317-
<div className={styles.inputInfoPanel}>
318-
<h2>
319-
<span>{toolCallInfo.serverInfo.name}</span>
320-
<span className={styles.toolName}>{toolCallInfo.tool.name}</span>
321-
{onRequestClose && !isDestroying && (
322-
<button
323-
className={styles.closeButton}
324-
onClick={onRequestClose}
325-
title="Close"
326-
>
327-
×
328-
</button>
329-
)}
330-
</h2>
331-
<JsonBlock value={toolCallInfo.input} />
332-
</div>
333-
)}
334-
<div className={isApp ? styles.appOutputPanel : styles.outputInfoPanel}>
335-
{/* For apps, show header above the app: ServerName:tool_name */}
336-
{isApp && (
337-
<div className={styles.appHeader}>
338-
<span>{toolCallInfo.serverInfo.name}:<span className={styles.toolName}>{toolCallInfo.tool.name}</span></span>
339-
{onRequestClose && !isDestroying && (
340-
<button
341-
className={styles.closeButton}
342-
onClick={onRequestClose}
343-
title="Close"
344-
>
345-
×
346-
</button>
347-
)}
348-
</div>
317+
{/* Row 1: Header with server:tool name and close button */}
318+
<div className={styles.appHeader}>
319+
<span>{toolCallInfo.serverInfo.name}:<span className={styles.toolName}>{toolCallInfo.tool.name}</span></span>
320+
{onRequestClose && !isDestroying && (
321+
<button
322+
className={styles.closeButton}
323+
onClick={onRequestClose}
324+
title="Close"
325+
>
326+
×
327+
</button>
349328
)}
329+
</div>
330+
331+
{/* Row 2: Tool Input */}
332+
<CollapsiblePanel icon="📥" label="Tool Input" content={inputJson} />
333+
334+
{/* Row 3: App iframe (if app) */}
335+
{isApp && (
350336
<ErrorBoundary>
351337
<Suspense fallback="Loading...">
352-
{
353-
isApp
354-
? <AppIFramePanel
355-
toolCallInfo={toolCallInfo}
356-
isDestroying={isDestroying}
357-
onTeardownComplete={onCloseComplete}
358-
/>
359-
: <ToolResultPanel toolCallInfo={toolCallInfo} />
360-
}
338+
<AppIFramePanel
339+
toolCallInfo={toolCallInfo}
340+
isDestroying={isDestroying}
341+
onTeardownComplete={onCloseComplete}
342+
/>
361343
</Suspense>
362344
</ErrorBoundary>
363-
</div>
364-
</div>
365-
);
366-
}
367-
345+
)}
368346

369-
function JsonBlock({ value }: { value: object }) {
370-
return (
371-
<pre className={styles.jsonBlock}>
372-
<code>{JSON.stringify(value, null, 2)}</code>
373-
</pre>
347+
{/* Row 4: Tool Result */}
348+
<ErrorBoundary>
349+
<Suspense fallback="Loading result...">
350+
<ToolResultPanel toolCallInfo={toolCallInfo} />
351+
</Suspense>
352+
</ErrorBoundary>
353+
</div>
374354
);
375355
}
376356

@@ -421,7 +401,6 @@ function AppIFramePanel({ toolCallInfo, isDestroying, onTeardownComplete }: AppI
421401
const iframeRef = useRef<HTMLIFrameElement | null>(null);
422402
const appBridgeRef = useRef<ReturnType<typeof newAppBridge> | null>(null);
423403
const [modelContext, setModelContext] = useState<ModelContext | null>(null);
424-
const [toolResult, setToolResult] = useState<object | null>(null);
425404
const [messages, setMessages] = useState<AppMessage[]>([]);
426405
const [displayMode, setDisplayMode] = useState<"inline" | "fullscreen">("inline");
427406

@@ -452,8 +431,6 @@ function AppIFramePanel({ toolCallInfo, isDestroying, onTeardownComplete }: AppI
452431
});
453432
});
454433

455-
// Track tool result for display
456-
toolCallInfo.resultPromise.then(setToolResult).catch(() => {});
457434
}, [toolCallInfo]);
458435

459436
// Graceful teardown: wait for guest to respond before unmounting
@@ -501,9 +478,6 @@ function AppIFramePanel({ toolCallInfo, isDestroying, onTeardownComplete }: AppI
501478
: "";
502479
const fullContext = [contextText, contextJson].filter(Boolean).join("\n\n");
503480

504-
const inputJson = JSON.stringify(toolCallInfo.input, null, 2);
505-
const resultJson = toolResult ? JSON.stringify(toolResult, null, 2) : null;
506-
507481
// Format messages
508482
const formatMessage = (m: AppMessage) => {
509483
const content = m.content.map(formatContentBlock).join("\n");
@@ -517,11 +491,7 @@ function AppIFramePanel({ toolCallInfo, isDestroying, onTeardownComplete }: AppI
517491

518492
return (
519493
<div className={panelClassName}>
520-
<CollapsiblePanel icon="📥" label="Tool Input" content={inputJson} />
521494
<iframe ref={iframeRef} />
522-
{resultJson && (
523-
<CollapsiblePanel icon="📤" label="Tool Result" content={resultJson} />
524-
)}
525495
{messages.length > 0 && (
526496
<CollapsiblePanel
527497
icon="💬"
@@ -543,7 +513,8 @@ interface ToolResultPanelProps {
543513
}
544514
function ToolResultPanel({ toolCallInfo }: ToolResultPanelProps) {
545515
const result = use(toolCallInfo.resultPromise);
546-
return <JsonBlock value={result} />;
516+
const resultJson = JSON.stringify(result, null, 2);
517+
return <CollapsiblePanel icon="📤" label="Tool Result" content={resultJson} />;
547518
}
548519

549520

0 commit comments

Comments
 (0)