In Chapter 3: Prompt Configuration, we taught the AI how to behave and gave it instructions on how to format previews.
But instructions aren't enough. Even if you tell a contractor "only paint the wall," they might accidentally paint the window too. We need a safety inspector.
In this chapter, we will build the Preview Feature Logic. This is the code that accepts the visual content (HTML or Markdown) from the AI, inspects it for safety, and prepares it for the user.
Imagine you are asking the AI to design a button for your website.
Without Previews: The AI asks: "Which button do you want?"
"Blue with rounded corners""Red with square corners"You have to imagine what these look like. It requires mental effort.
With Previews: You see the actual rendered HTML button right next to the choice. You don't have to read; you just look and click.
However, rendering HTML from an AI is dangerous. What if the AI includes a <script> tag that deletes your files? Or a <body> tag that breaks your entire application layout?
Our goal is to allow the AI to send us a snippet of HTML, but strictly enforce that it is safe and contained.
The AI sends this (Good):
<button style="background: blue; border-radius: 5px;">
Click Me
</button>
The AI sends this (Bad - Security Risk):
<script>stealUserPasswords()</script>
<button>Click Me</button>
The AI sends this (Bad - Layout Breaker):
<html>
<body>My Button</body>
</html>
We need logic to accept the Good and reject the Bad.
validateHtmlPreview
To solve this, we create a helper function called validateHtmlPreview. It acts as a "Bouncer" at the door of our rendering engine.
It uses Regular Expressions (Regex) to scan the text string for forbidden patterns.
We only want a fragment (like a single brick), not a whole house.
function validateHtmlPreview(preview) {
// 1. Check for "whole document" tags
if (/<\s*(html|body|!doctype)\b/i.test(preview)) {
return 'Error: Must be a fragment, not a full document.';
}
// ... continue checks
}
Why: If we render a <body> tag inside our existing application, it creates a "Russian Doll" situation that ruins the UI layout.
We want visual previews, not executable code or global styling rules.
// 2. Check for executable or global style tags
if (/<\s*(script|style)\b/i.test(preview)) {
return 'Error: No <script> or <style> tags allowed.';
}
Why:
<script>: Security risk (XSS attacks).<style>: Could accidentally change the color of the entire application, not just the preview.Finally, if we are expecting HTML, we must ensure it actually looks like HTML (contains tags).
// 3. Ensure there is at least one tag
if (!/<[a-z][^>]*>/i.test(preview)) {
return 'Error: Content must contain HTML tags (e.g., <div>).';
}
return null; // No errors found!
}
Now that we have our validator, we hook it into the Tool Definition we built in Chapter 2: Tool Definition.
We use the validateInput method. This runs automatically after the Zod schema check but before the tool actually runs.
async validateInput({ questions }) {
// Only check if we are in HTML mode
if (getQuestionPreviewFormat() !== 'html') return { result: true };
for (const q of questions) {
for (const opt of q.options) {
// Run our Bouncer function
const err = validateHtmlPreview(opt.preview);
if (err) return { result: false, message: err };
}
}
return { result: true };
}
If validateInput returns false, the AI receives an error message explaining exactly what it did wrong (e.g., "No