Explaining Intelligent UI with Joplo: how AI answers with an interface

This post is adapted from the author's long-form Chinese article, Intelligent UI 实现详解 (Intelligent UI, explained in detail). The original is about 10,000 words, with traffic captures and a working reimplementation (intelligent-ui-demo). Here we keep the main line and turn the key flows into a playable Joplo demo.

Ask an AI to "work out my mortgage" and it can reply with a paragraph, or with a calculator: drag a slider and the monthly payment changes right away. The second kind of answer is what this post is about. It looks like "the AI output a component", but it has to answer three questions:

  1. What language describes the interface?
  2. How do you stream it while it is being written?
  3. How do you display it safely?

Watch the demo first

Use the play controls below the stage to step through the demo, or open it full screen.Open the demo in a new window

The demo follows one mortgage calculator through the normal flow, how errors from the AI are tolerated, the sandbox's security boundary, and a comparison of the "write data" and "write a program" approaches. The sections below follow the demo, so you can read and play along.

Two approaches: write data, or write a program

There are two ways to let an AI output an interactive interface.

Write data: the AI outputs a list of components and the browser assembles it from components it already has. A2UI is the reference example.

  • Safe by construction: the list contains no executable code, and components can only come from an allowlisted catalog.
  • Works in native apps.
  • The cost: computation goes back to the AI. Every time the user changes a parameter, the model is asked again.

Write a program: the AI outputs a small program and the browser runs it locally. ChatGPT's Intelligent UI is the reference example.

  • Computation happens instantly and locally, so interactions can be rich.
  • The cost: it must be isolated; a mistake in the program can stop the whole interface from running; and it is hard to use in native apps.

The names are often mixed up, but they are three layers: A2UI is an interface format (write data). AG-UI is a transport protocol with events such as RUN_STARTED, TEXT_MESSAGE_CONTENT, TOOL_CALL_ARGS and STATE_DELTA (JSON Patch); it can carry formats like A2UI, Open-JSON-UI and MCP-UI. Intelligent UI is the complete engineering pipeline of the "write a program" approach.

The Intelligent UI pipeline: one "work out my mortgage"

1. The AI writes DIL source, token by token

The model outputs Markdown text with @body directives and JSX-like tags in between:

{@body const [amount, setAmount] = DIL.useState(100)}
{@body const monthly = amount * 10000 * r * (1+r)**n / ((1+r)**n - 1)}
<slider label="Loan amount (10k CNY)" value={amount} onChange={setAmount} min={10} max={500}/>

2. A server-side compiler that always starts over and never bails out

The model writes one token at a time, so the compiler's input is almost always incomplete. Every short interval it recompiles everything written so far, and:

  • adds a key to every useState, such as DIL.useState(100, {key: "amount"}), so state survives a refresh;
  • wraps elements that contain expressions in a guard, so a failed calculation only affects that one element:
function __dilSafe(evaluate, fallback) {
	try {
		return evaluate();
	} catch {
		return fallback;
	}
}
  • records a diagnostic instead of failing, for example {"code":"unclosed_block","action":"recovered_parse","line":66}.

One capture in the original article contained 338 diagnostics, 134 program updates and 56 guards.

3. Streaming patches over SSE

The server sends a single message type: a delta patch. Text and program code are two lines running at the same time:

{"p": "/message/content/parts/0", "o": "append", "v": "…"}
{"p": "/message/metadata/model_dil_v2/code", "o": "replace", "v": "…"}

A dropped connection can resume. One answer's connection was about 2.5 MB and lasted tens of seconds.

4. Two layers of sandbox

The browser hands the program to a sandbox:

  • the outer layer is a cross-origin iframe whose CSP starts from default-src 'none', with no network access;
  • the inner layer is a Web Worker that runs the program on its own thread and only hands back a tree of interface data, such as {"t":"box","p":{"gap":3},"c":[…]}. If it loops forever, the Worker is simply terminated.

Event handlers are just IDs in the tree ({"__dilFn":"fn1"}). When the program needs one of its five capabilities (copy, open a link, ask a follow-up, open an entity's details, call a plugin), it also gets only an ID: calling it sends a message, and the main page validates the arguments before acting.

The interface uses components provided by the host: 30 always loaded, 283 on demand. The AI can only reference them by name; components that are not confirmed yet show a placeholder box.

5. Interaction never touches the server

When the user drags the slider, the page tells the sandbox "fn1 fired with 300". The sandbox recomputes and returns a new tree; the page diffs old and new trees and only updates what changed. None of this goes through the server.

6. State flows into the next turn

Named state is reported to the server (POST …/dil/view_state). When the user then asks "with these parameters, what is the monthly payment with equal principal?", the next request carries these state snapshots, and the AI answers with 3 million and 20 years directly.

The same calculator, written in A2UI

On the "write data" path, the AI first sends createSurface, then updateComponents with a flat list of components (a root is required), then updateDataModel with the initial data.

When the user drags the slider, the local /amount changes but the monthly payment does not, because the list has no calculation logic. When the user clicks "Recalculate", an action with its context goes back to the AI, which sends another updateDataModel. Each recalculation is one round trip.

That is the clearest difference between the two: A2UI makes a round trip per recalculation, while Intelligent UI recalculates instantly on the device.

When the AI gets it wrong

The "write a program" approach is most fragile to syntax errors: the whole program fails, and every few tokens the AI writes, it fails again, so the user stares at a blank space. Common mistakes the author hit when reimplementing it with DeepSeek, and how each is handled:

  • curly braces inside code or pre: content in these two tags is always plain text;
  • a directive missing its closing curly brace: cut it off at the end of the line;
  • an undeclared variable (value={usage}): when a matching usage / setUsage pair is seen, add the declaration;
  • the whole answer wrapped in an html code fence: remove the fence and record a diagnostic.

With two extra safeguards, all 12 real outputs and all 30,184 "half-written" versions ran, and none fell back to plain text. One more pitfall is thinking mode: the first screen takes 20–30 seconds longer, so always show progress.

The author's takeaways

  • Make the compiler very tolerant, and keep any failure contained to one element (the guard);
  • put complete, runnable examples in the instructions for the AI, and test them;
  • isolate with two sandbox layers and a CSP that blocks the network; give capabilities through proxies and validate arguments on the main page to prevent prototype pollution;
  • stream with "snapshot + patches" and support resume;
  • diff old and new trees when updating, so inputs keep their text and cursor.

Why explain it with Joplo

The hardest part of this topic is not any single concept. It is that one thing happens after another: the model writes, the compiler catches up, patches stream, the sandbox computes, and state flows back into the conversation. A screenshot freezes one moment; a screen recording cannot be edited.

Joplo fits this kind of technical explanation:

  • Step-by-step playback: each step has a one-line caption, and viewers can pause, step back or go full screen;
  • Option comparison: the same calculator written as "write data" and "write a program", switched on the board;
  • Cases: the normal flow, AI mistakes and the security boundary are separate cases, so none gets skipped;
  • The source is TSX: the explanation lives next to the code, and an agent can change exactly one place;
  • One-command sharing: run npx joplo share when you are done and you get a link like the one above.

To make your own, start here:

npx joplo@latest init my-demo
cd my-demo
npm install
npx joplo dev --open

Original article and further reading