Welcome to the final chapter of our series!
In Chapter 5: Result Processing & Serialization, we finished the "Backend" work. We cleaned up the messy data streams into a neat structure for the AI to understand.
But what about the Human?
Currently, if you ran the tool, the terminal would sit blank for a few seconds while the AI works, and then suddenly spit out an answer. This feels broken. Users need to know what is happening right now.
This brings us to Interface Rendering (UI).
Think of the WebSearchTool as a car engine.
Even if the engine is perfect, a car without a dashboard is scary to drive. You don't know if it's on, how fast you're going, or if you're about to run out of gas.
In this project, we use React (specifically a library called Ink) to render text in the terminal. We need to build three specific "Gauges" for our dashboard:
renderToolUseMessage)When the AI first decides "I need to search the web," we want to show a static message to the user confirming this decision.
We define this in UI.tsx.
// UI.tsx
import { Text } from '../../ink.js'
export function renderToolUseMessage(input) {
const { query } = input
// If there is no query yet, show nothing
if (!query) return null
// Render the query simply, e.g., "Apple Stock"
return `"${query}"`
}
Explanation:
Use Tool: Web Search "Apple Stock".renderToolUseProgressMessage)
This is the most interactive part. In Chapter 4: Streaming Execution Strategy, we emitted events like query_update and search_results_received.
Now, we need a component that listens to those events and updates the screen in real-time.
We look at the latest progress message and decide what to show.
// UI.tsx
export function renderToolUseProgressMessage(progressMessages) {
// 1. Get the most recent update
const lastProgress = progressMessages[progressMessages.length - 1]
const data = lastProgress?.data
if (!data) return null
// 2. Decide what to render based on the event type
switch (data.type) {
case 'query_update':
return <Text dimColor>Searching: {data.query}</Text>
case 'search_results_received':
return <Text dimColor>Found {data.resultCount} results...</Text>
default:
return null
}
}
Explanation:
progressMessages: An array of every update sent so far. We usually only care about the last one.switch (data.type): We switch visuals based on what is happening.<Text dimColor>: This is a React/Ink component. dimColor makes the text grey so it's not distracting.Searching: Apple... (Grey text).Found 5 results....renderToolResultMessage)Once the tool is finished, the progress bar disappears. We want to leave a permanent record of what happened so the user can scroll back and see it later.
We use the cleaned Output object we created in Chapter 5.
First, we need a tiny helper to count how many valid links we found.
// UI.tsx
function getSearchSummary(results) {
let searchCount = 0
for (const result of results) {
// Only count actual objects, not error strings
if (result && typeof result !== 'string') {
searchCount++
}
}
return { searchCount }
}
Now we display the stats.
// UI.tsx
export function renderToolResultMessage(output) {
const { searchCount } = getSearchSummary(output.results ?? [])
// Format time: "1.2s" or "800ms"
const time = output.durationSeconds >= 1
? `${Math.round(output.durationSeconds)}s`
: `${Math.round(output.durationSeconds * 1000)}ms`
return (
<Box>
<Text>Did {searchCount} searches in {time}</Text>
</Box>
)
}
Explanation:
Output object.<Box> containing the summary text.We have built the visual components, but they are just floating functions. We need to attach them to our Tool Definition so the system knows when to use them.
We do this back in WebSearchTool.ts (referencing Chapter 1).
// WebSearchTool.ts
import {
renderToolUseMessage,
renderToolUseProgressMessage,
renderToolResultMessage
} from './UI.js'
export const WebSearchTool = buildTool({
name: 'web_search',
// ... other configuration ...
// Attach the UI functions here!
renderToolUseMessage,
renderToolUseProgressMessage,
renderToolResultMessage,
})
Explanation:
By importing the functions and adding them to the buildTool object, we tell the main application: "When you use this tool, use these specific functions to draw the dashboard."
Here is the full flow of how the User Interface updates during a tool run.
Congratulations! You have completed the construction of the WebSearchTool.
Let's review what we built across these 6 chapters:
You now have a fully functional, production-grade tool that allows an AI to browse the internet, streaming results in real-time, while keeping the user informed every step of the way.
End of Tutorial.
Generated by Code IQ