Welcome back! In the previous chapter, Operation Schemas & Validation, we acted as "Customs Officers," ensuring that every request the AI makes is valid and safe.
Now that we have valid data and the tool is running, we face a new problem: How do we show the results?
Imagine you are driving a car.
If the AI (or the human developer watching the AI) had to read raw JSON database dumps for every action, it would be overwhelming.
The Goal: We need a UI (User Interface) that acts as a dashboard. It should take complex data and render simple, human-readable summaries in the terminal.
We use a library called Ink (React for the Command Line) to build our UI.
In our chat interface, every tool interaction has two parts:
Sometimes a search finds 1 result. Sometimes it finds 500.
When the AI decides to call a function (like goToDefinition), we want to show a concise status update.
Instead of showing { op: 'goToDefinition', line: 15, char: 8 }, we want to show:
operation: "goToDefinition", symbol: "processData", in: "utils.ts"
Here is how renderToolUseMessage handles this in UI.tsx:
// UI.tsx
export function renderToolUseMessage(input: Partial<Input>) {
// 1. If we have a specific file and position...
if (input.filePath && input.line && input.character) {
// 2. Try to find the actual word/symbol at that spot
const symbol = getSymbolAtPosition(
input.filePath,
input.line - 1,
input.character - 1
);
// 3. Return a readable string
if (symbol) {
return `operation: "${input.operation}", symbol: "${symbol}"`;
}
}
// ... fallback logic
}
Explanation: We do a little extra work here. We don't just repeat the line number; we look up the symbol (the word) at that line to make the message friendlier.
Once the LSP server replies, we need to display the findings. This is handled by renderToolResultMessage.
We prioritize clarity. We use a helper component called LSPResultSummary.
// UI.tsx
export function renderToolResultMessage(output: Output, /*...*/) {
// If we have count data (meaning the operation was successful)
if (output.resultCount !== undefined) {
return (
<LSPResultSummary
operation={output.operation}
resultCount={output.resultCount}
fileCount={output.fileCount}
content={output.result}
/>
);
}
// Fallback for errors or simple messages
return <Text>{output.result}</Text>;
}
How does the component decide whether to show a massive list or a tiny summary? Let's trace the decision flow.
To make the UI feel natural, we map technical operation names to English grammar. We don't want to say "Found 5 goToDefinition(s)."
// UI.tsx
const OPERATION_LABELS = {
goToDefinition: {
singular: 'definition',
plural: 'definitions'
},
findReferences: {
singular: 'reference',
plural: 'references'
},
// ... maps other operations
};
The LSPResultSummary component combines the count, the label, and the collapsing logic.
// UI.tsx (Simplified)
function LSPResultSummary({ operation, resultCount, fileCount }) {
// 1. Get the English label
const config = OPERATION_LABELS[operation];
const label = resultCount === 1 ? config.singular : config.plural;
// 2. Construct the main message
// Example: "Found 5 references"
const text = <Text>Found <Text bold>{resultCount}</Text> {label}</Text>;
// 3. Add expand hint if there are many results
const expandHint = resultCount > 0 ? <CtrlOToExpand /> : null;
return (
<Box>
{text} {expandHint}
</Box>
);
}
Explanation:
<CtrlOToExpand /> component, which tells the user they can press a key to see the full raw data if they really need to.By using these components, the user experience changes dramatically.
Without UI Components:
{
"uri": "file:///src/app.ts",
"range": { "start": { "line": 10, "character": 5 }, "end": { ... } }
}
With UI Components:
Found 1 definition in app.ts
Line 10: class Application { ...
This abstraction layer keeps the developer focused on coding, not decoding JSON.
In this chapter, we learned how to build a visual "Dashboard" for our tool:
Now the user can see the result clearly. But waitβwhere did the actual code content come from? The LSP often just returns line numbers, not the code itself.
In the next chapter, we will learn how we grab the actual source code text to display to the user.
Next Chapter: Context Extraction
Generated by Code IQ